From a6f1e449004e0fb6843e395dee2e1c9d56255811 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:45:37 -0500 Subject: [PATCH 01/34] refactor: use upgraded btcd library to handle Taproot address (#3039) * use upgraded btcd library to handle Taproot address; cleanup previous workaround code * add changelog entry * add address types as comments to function call btcutil.DecodeAddress() --- changelog.md | 1 + e2e/e2etests/test_bitcoin_withdraw_taproot.go | 4 +- pkg/chains/address.go | 17 +- pkg/chains/address_taproot.go | 217 ------------------ pkg/chains/address_taproot_test.go | 79 ------- pkg/chains/address_test.go | 2 +- zetaclient/chains/bitcoin/fee.go | 3 +- zetaclient/chains/bitcoin/fee_test.go | 6 +- zetaclient/chains/bitcoin/signer/signer.go | 4 +- .../bitcoin/signer/signer_keysign_test.go | 3 +- .../chains/bitcoin/signer/signer_test.go | 11 +- zetaclient/chains/bitcoin/tx_script.go | 15 +- 12 files changed, 25 insertions(+), 337 deletions(-) delete mode 100644 pkg/chains/address_taproot.go delete mode 100644 pkg/chains/address_taproot_test.go diff --git a/changelog.md b/changelog.md index babe5d10d8..37831d8857 100644 --- a/changelog.md +++ b/changelog.md @@ -31,6 +31,7 @@ * [2890](https://github.com/zeta-chain/node/pull/2890) - refactor `MsgUpdateChainInfo` to accept a single chain, and add `MsgRemoveChainInfo` to remove a chain * [2899](https://github.com/zeta-chain/node/pull/2899) - remove btc deposit fee v1 and improve unit tests * [2952](https://github.com/zeta-chain/node/pull/2952) - add error_message to cctx.status +* [3039](https://github.com/zeta-chain/node/pull/3039) - use `btcd` native APIs to handle Bitcoin Taproot address ### Tests diff --git a/e2e/e2etests/test_bitcoin_withdraw_taproot.go b/e2e/e2etests/test_bitcoin_withdraw_taproot.go index d9d0b4cf48..f675a88c43 100644 --- a/e2e/e2etests/test_bitcoin_withdraw_taproot.go +++ b/e2e/e2etests/test_bitcoin_withdraw_taproot.go @@ -1,10 +1,10 @@ package e2etests import ( + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/e2e/runner" - "github.com/zeta-chain/node/pkg/chains" ) func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { @@ -15,7 +15,7 @@ func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { // parse arguments and withdraw BTC defaultReceiver := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh" receiver, amount := parseBitcoinWithdrawArgs(r, args, defaultReceiver) - _, ok := receiver.(*chains.AddressTaproot) + _, ok := receiver.(*btcutil.AddressTaproot) require.True(r, ok, "Invalid receiver address specified for TestBitcoinWithdrawTaproot.") withdrawBTCZRC20(r, receiver, amount) diff --git a/pkg/chains/address.go b/pkg/chains/address.go index 1f2ddddad8..8c5ba45db3 100644 --- a/pkg/chains/address.go +++ b/pkg/chains/address.go @@ -54,6 +54,7 @@ func ConvertRecoverToError(r interface{}) error { // DecodeBtcAddress decodes a BTC address from a given string and chainID func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Address, err error) { + // prevent potential panic from 'btcutil.DecodeAddress' defer func() { if r := recover(); r != nil { err = ConvertRecoverToError(r) @@ -68,19 +69,15 @@ func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Addre if chainParams == nil { return nil, fmt.Errorf("chain params not found") } - // test taproot address type - address, err = DecodeTaprootAddress(inputAddress) - if err == nil { - if address.IsForNet(chainParams) { - return address, nil - } - return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name) - } - // test taproot address failed; continue testing other types: P2WSH, P2WPKH, P2SH, P2PKH + + // try decoding input address as a Bitcoin address. + // this will decode all types of Bitcoin addresses: P2PKH, P2SH, P2WPKH, P2WSH, P2TR, etc. address, err = btcutil.DecodeAddress(inputAddress, chainParams) if err != nil { return nil, fmt.Errorf("decode address failed: %s, for input address %s", err.Error(), inputAddress) } + + // address must match the network ok := address.IsForNet(chainParams) if !ok { return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name) @@ -109,7 +106,7 @@ func DecodeSolanaWalletAddress(inputAddress string) (pk solana.PublicKey, err er func IsBtcAddressSupported(addr btcutil.Address) bool { switch addr.(type) { // P2TR address - case *AddressTaproot, + case *btcutil.AddressTaproot, // P2WSH address *btcutil.AddressWitnessScriptHash, // P2WPKH address diff --git a/pkg/chains/address_taproot.go b/pkg/chains/address_taproot.go deleted file mode 100644 index 796c3fde85..0000000000 --- a/pkg/chains/address_taproot.go +++ /dev/null @@ -1,217 +0,0 @@ -package chains - -import ( - "bytes" - "errors" - "fmt" - "strings" - - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/bech32" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" -) - -// taproot address type - -type AddressSegWit struct { - hrp string - witnessVersion byte - witnessProgram []byte -} - -type AddressTaproot struct { - AddressSegWit -} - -var _ btcutil.Address = &AddressTaproot{} - -// NewAddressTaproot returns a new AddressTaproot. -func NewAddressTaproot(witnessProg []byte, - net *chaincfg.Params) (*AddressTaproot, error) { - return newAddressTaproot(net.Bech32HRPSegwit, witnessProg) -} - -// newAddressTaproot is an internal helper function to create an -// AddressWitnessScriptHash with a known human-readable part, rather than -// looking it up through its parameters. -func newAddressTaproot(hrp string, witnessProg []byte) (*AddressTaproot, error) { - // Check for valid program length for witness version 1, which is 32 - // for P2TR. - if len(witnessProg) != 32 { - return nil, errors.New("witness program must be 32 bytes for " + - "p2tr") - } - - addr := &AddressTaproot{ - AddressSegWit{ - hrp: strings.ToLower(hrp), - witnessVersion: 0x01, - witnessProgram: witnessProg, - }, - } - - return addr, nil -} - -// EncodeAddress returns the bech32 (or bech32m for SegWit v1) string encoding -// of an AddressSegWit. -// -// NOTE: This method is part of the Address interface. -func (a AddressSegWit) EncodeAddress() string { - str, err := encodeSegWitAddress( - a.hrp, a.witnessVersion, a.witnessProgram[:], - ) - if err != nil { - return "" - } - return str -} - -// encodeSegWitAddress creates a bech32 (or bech32m for SegWit v1) encoded -// address string representation from witness version and witness program. -func encodeSegWitAddress(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) { - // Group the address bytes into 5 bit groups, as this is what is used to - // encode each character in the address string. - converted, err := bech32.ConvertBits(witnessProgram, 8, 5, true) - if err != nil { - return "", err - } - - // Concatenate the witness version and program, and encode the resulting - // bytes using bech32 encoding. - combined := make([]byte, len(converted)+1) - combined[0] = witnessVersion - copy(combined[1:], converted) - - var bech string - switch witnessVersion { - case 0: - bech, err = bech32.Encode(hrp, combined) - - case 1: - bech, err = bech32.EncodeM(hrp, combined) - - default: - return "", fmt.Errorf("unsupported witness version %d", - witnessVersion) - } - if err != nil { - return "", err - } - - // Check validity by decoding the created address. - _, version, program, err := decodeSegWitAddress(bech) - if err != nil { - return "", fmt.Errorf("invalid segwit address: %v", err) - } - - if version != witnessVersion || !bytes.Equal(program, witnessProgram) { - return "", fmt.Errorf("invalid segwit address") - } - - return bech, nil -} - -// decodeSegWitAddress parses a bech32 encoded segwit address string and -// returns the witness version and witness program byte representation. -func decodeSegWitAddress(address string) (string, byte, []byte, error) { - // Decode the bech32 encoded address. - hrp, data, bech32version, err := bech32.DecodeGeneric(address) - if err != nil { - return "", 0, nil, err - } - - // The first byte of the decoded address is the witness version, it must - // exist. - if len(data) < 1 { - return "", 0, nil, fmt.Errorf("no witness version") - } - - // ...and be <= 16. - version := data[0] - if version > 16 { - return "", 0, nil, fmt.Errorf("invalid witness version: %v", version) - } - - // The remaining characters of the address returned are grouped into - // words of 5 bits. In order to restore the original witness program - // bytes, we'll need to regroup into 8 bit words. - regrouped, err := bech32.ConvertBits(data[1:], 5, 8, false) - if err != nil { - return "", 0, nil, err - } - - // The regrouped data must be between 2 and 40 bytes. - if len(regrouped) < 2 || len(regrouped) > 40 { - return "", 0, nil, fmt.Errorf("invalid data length") - } - - // For witness version 0, address MUST be exactly 20 or 32 bytes. - if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 { - return "", 0, nil, fmt.Errorf("invalid data length for witness "+ - "version 0: %v", len(regrouped)) - } - - // For witness version 0, the bech32 encoding must be used. - if version == 0 && bech32version != bech32.Version0 { - return "", 0, nil, fmt.Errorf("invalid checksum expected bech32 " + - "encoding for address with witness version 0") - } - - // For witness version 1, the bech32m encoding must be used. - if version == 1 && bech32version != bech32.VersionM { - return "", 0, nil, fmt.Errorf("invalid checksum expected bech32m " + - "encoding for address with witness version 1") - } - - return hrp, version, regrouped, nil -} - -// ScriptAddress returns the witness program for this address. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) ScriptAddress() []byte { - return a.witnessProgram[:] -} - -// IsForNet returns whether the AddressSegWit is associated with the passed -// bitcoin network. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) IsForNet(net *chaincfg.Params) bool { - return a.hrp == net.Bech32HRPSegwit -} - -// String returns a human-readable string for the AddressWitnessPubKeyHash. -// This is equivalent to calling EncodeAddress, but is provided so the type -// can be used as a fmt.Stringer. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) String() string { - return a.EncodeAddress() -} - -// DecodeTaprootAddress decodes taproot address only and returns error on non-taproot address -func DecodeTaprootAddress(addr string) (*AddressTaproot, error) { - hrp, version, program, err := decodeSegWitAddress(addr) - if err != nil { - return nil, err - } - if version != 1 { - return nil, errors.New("invalid witness version; taproot address must be version 1") - } - return &AddressTaproot{ - AddressSegWit{ - hrp: hrp, - witnessVersion: version, - witnessProgram: program, - }, - }, nil -} - -// PayToWitnessTaprootScript creates a new script to pay to a version 1 -// (taproot) witness program. The passed hash is expected to be valid. -func PayToWitnessTaprootScript(rawKey []byte) ([]byte, error) { - return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(rawKey).Script() -} diff --git a/pkg/chains/address_taproot_test.go b/pkg/chains/address_taproot_test.go deleted file mode 100644 index c3742dcefc..0000000000 --- a/pkg/chains/address_taproot_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package chains - -import ( - "encoding/hex" - "testing" - - "github.com/btcsuite/btcd/chaincfg" - "github.com/stretchr/testify/require" -) - -func TestAddressTaproot(t *testing.T) { - { - // should parse mainnet taproot address - addrStr := "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.MainNetParams)) - } - { - // should parse testnet taproot address - addrStr := "tb1pzeclkt6upu8xwuksjcz36y4q56dd6jw5r543eu8j8238yaxpvcvq7t8f33" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.TestNet3Params)) - } - { - // should parse regtest taproot address - addrStr := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.RegressionNetParams)) - } - - { - // should fail to parse invalid taproot address - // should parse mainnet taproot address - addrStr := "bc1qysd4sp9q8my59ul9wsf5rvs9p387hf8vfwatzu" - _, err := DecodeTaprootAddress(addrStr) - require.Error(t, err) - } - { - var witnessProg [32]byte - for i := 0; i < 32; i++ { - witnessProg[i] = byte(i) - } - _, err := newAddressTaproot("bcrt", witnessProg[:]) - require.NoError(t, err) - //t.Logf("addr: %v", addr) - } - { - // should create correct taproot address from given witness program - // these hex string comes from link - // https://mempool.space/tx/41f7cbaaf9a8d378d09ee86de32eebef455225520cb71015cc9a7318fb42e326 - witnessProg, err := hex.DecodeString("af06f3d4c726a3b952f2f648d86398af5ddfc3df27aa531d97203987add8db2e") - require.NoError(t, err) - addr, err := NewAddressTaproot(witnessProg[:], &chaincfg.MainNetParams) - require.NoError(t, err) - require.Equal(t, addr.EncodeAddress(), "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c") - } - { - // should give correct ScriptAddress for taproot address - // example comes from - // https://blockstream.info/tx/09298a2f32f5267f419aeaf8a58c4807dcf6cac3edb59815a3b129cd8f1219b0?expand - addrStr := "bc1p6pls9gpm24g8ntl37pajpjtuhd3y08hs5rnf9a4n0wq595hwdh9suw7m2h" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal( - t, - "d07f02a03b555079aff1f07b20c97cbb62479ef0a0e692f6b37b8142d2ee6dcb", - hex.EncodeToString(addr.ScriptAddress()), - ) - } -} diff --git a/pkg/chains/address_test.go b/pkg/chains/address_test.go index 38991bc7ab..bba2db0bc9 100644 --- a/pkg/chains/address_test.go +++ b/pkg/chains/address_test.go @@ -179,7 +179,7 @@ func Test_IsBtcAddressSupported_P2TR(t *testing.T) { // it should be a taproot address addr, err := DecodeBtcAddress(tt.addr, tt.chainId) require.NoError(t, err) - _, ok := addr.(*AddressTaproot) + _, ok := addr.(*btcutil.AddressTaproot) require.True(t, ok) // it should be supported diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index 58297f37d6..f56a479364 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/pkg/errors" - "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc" "github.com/zeta-chain/node/zetaclient/chains/interfaces" clientcommon "github.com/zeta-chain/node/zetaclient/common" @@ -104,7 +103,7 @@ func EstimateOutboundSize(numInputs uint64, payees []btcutil.Address) (uint64, e // GetOutputSizeByAddress returns the size of a tx output in bytes by the given address func GetOutputSizeByAddress(to btcutil.Address) (uint64, error) { switch addr := to.(type) { - case *chains.AddressTaproot: + case *btcutil.AddressTaproot: if addr == nil { return 0, nil } diff --git a/zetaclient/chains/bitcoin/fee_test.go b/zetaclient/chains/bitcoin/fee_test.go index 8b1f54e5d5..82f60ff0ef 100644 --- a/zetaclient/chains/bitcoin/fee_test.go +++ b/zetaclient/chains/bitcoin/fee_test.go @@ -66,7 +66,7 @@ func generateKeyPair(t *testing.T, net *chaincfg.Params) (*btcec.PrivateKey, btc addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, net) require.NoError(t, err) //fmt.Printf("New address: %s\n", addr.EncodeAddress()) - pkScript, err := PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) require.NoError(t, err) return privateKey, addr, pkScript } @@ -83,7 +83,7 @@ func getTestAddrScript(t *testing.T, scriptType string) btcutil.Address { // createPkScripts creates 10 random amount of scripts to the given address 'to' func createPkScripts(t *testing.T, to btcutil.Address, repeat int) ([]btcutil.Address, [][]byte) { - pkScript, err := PayToAddrScript(to) + pkScript, err := txscript.PayToAddrScript(to) require.NoError(t, err) addrs := []btcutil.Address{} @@ -261,7 +261,7 @@ func TestOutboundSizeXIn3Out(t *testing.T) { func TestGetOutputSizeByAddress(t *testing.T) { // test nil P2TR address and non-nil P2TR address - nilP2TR := (*chains.AddressTaproot)(nil) + nilP2TR := (*btcutil.AddressTaproot)(nil) sizeNilP2TR, err := GetOutputSizeByAddress(nilP2TR) require.NoError(t, err) require.Equal(t, uint64(0), sizeNilP2TR) diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 7e49d4d675..90257e019e 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -158,7 +158,7 @@ func (signer *Signer) AddWithdrawTxOutputs( if err != nil { return err } - payToSelfScript, err := bitcoin.PayToAddrScript(tssAddrP2WPKH) + payToSelfScript, err := txscript.PayToAddrScript(tssAddrP2WPKH) if err != nil { return err } @@ -167,7 +167,7 @@ func (signer *Signer) AddWithdrawTxOutputs( // 2nd output: the payment to the recipient if !cancelTx { - pkScript, err := bitcoin.PayToAddrScript(to) + pkScript, err := txscript.PayToAddrScript(to) if err != nil { return err } diff --git a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go index 1ad50f0af5..a339b051dd 100644 --- a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go @@ -17,7 +17,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -96,7 +95,7 @@ func buildTX() (*wire.MsgTx, *txscript.TxSigHashes, int, int64, []byte, *btcec.P txIn := wire.NewTxIn(outpoint, nil, nil) tx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, nil, 0, 0, nil, nil, false, err } diff --git a/zetaclient/chains/bitcoin/signer/signer_test.go b/zetaclient/chains/bitcoin/signer/signer_test.go index b9decf25b1..17fb2dc3de 100644 --- a/zetaclient/chains/bitcoin/signer/signer_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_test.go @@ -20,7 +20,6 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/base" - "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -76,7 +75,7 @@ func (s *BTCSignerSuite) TestP2PH(c *C) { prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) c.Assert(err, IsNil) @@ -148,7 +147,7 @@ func (s *BTCSignerSuite) TestP2WPH(c *C) { prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) c.Assert(err, IsNil) txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) @@ -169,7 +168,7 @@ func (s *BTCSignerSuite) TestP2WPH(c *C) { txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) txSigHashes := txscript.NewTxSigHashes(redeemTx, txscript.NewCannedPrevOutputFetcher([]byte{}, 0)) - pkScript, err = bitcoin.PayToAddrScript(addr) + pkScript, err = txscript.PayToAddrScript(addr) c.Assert(err, IsNil) { @@ -240,7 +239,7 @@ func TestAddWithdrawTxOutputs(t *testing.T) { // tss address and script tssAddr, err := signer.TSS().BTCAddress(chains.BitcoinTestnet.ChainId) require.NoError(t, err) - tssScript, err := bitcoin.PayToAddrScript(tssAddr) + tssScript, err := txscript.PayToAddrScript(tssAddr) require.NoError(t, err) fmt.Printf("tss address: %s", tssAddr.EncodeAddress()) @@ -248,7 +247,7 @@ func TestAddWithdrawTxOutputs(t *testing.T) { receiver := "bc1qaxf82vyzy8y80v000e7t64gpten7gawewzu42y" to, err := chains.DecodeBtcAddress(receiver, chains.BitcoinMainnet.ChainId) require.NoError(t, err) - toScript, err := bitcoin.PayToAddrScript(to) + toScript, err := txscript.PayToAddrScript(to) require.NoError(t, err) // test cases diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index f5bc856d5d..9614010a65 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -36,17 +36,6 @@ const ( LengthScriptP2PKH = 25 ) -// PayToAddrScript creates a new script to pay a transaction output to a the -// specified address. -func PayToAddrScript(addr btcutil.Address) ([]byte, error) { - switch addr := addr.(type) { - case *chains.AddressTaproot: - return chains.PayToWitnessTaprootScript(addr.ScriptAddress()) - default: - return txscript.PayToAddrScript(addr) - } -} - // IsPkScriptP2TR checks if the given script is a P2TR script func IsPkScriptP2TR(script []byte) bool { return len(script) == LengthScriptP2TR && script[0] == txscript.OP_1 && script[1] == 0x20 @@ -91,7 +80,7 @@ func DecodeScriptP2TR(scriptHex string, net *chaincfg.Params) (string, error) { } witnessProg := script[2:] - receiverAddress, err := chains.NewAddressTaproot(witnessProg, net) + receiverAddress, err := btcutil.NewAddressTaproot(witnessProg, net) if err != nil { // should never happen return "", errors.Wrapf(err, "error getting address from script %s", scriptHex) } @@ -278,7 +267,7 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai // parse receiver address from vout var receiverVout string switch addr.(type) { - case *chains.AddressTaproot: + case *btcutil.AddressTaproot: receiverVout, err = DecodeScriptP2TR(vout.ScriptPubKey.Hex, chainParams) case *btcutil.AddressWitnessScriptHash: receiverVout, err = DecodeScriptP2WSH(vout.ScriptPubKey.Hex, chainParams) From 467d6914f13e98a5179dc42f7ba137d864ac2964 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:48:43 -0500 Subject: [PATCH 02/34] feat: integrate standard memo to Bitcoin inbound and E2E tests (#3025) * initial commit of btc revert address * add e2e tests for bitcoin standard memo deposit * add more unit tests * disable standard memo for Bitcoin mainnet * fix gosec * uncomment e2e tests * a few renamings; better comments and unit test * code refactor to make DecodeMemoBytes more redable * add more description for function; add unit test to ensure that standard memo is disabled for mainnet * revert func Processability() to pointer receiver --- changelog.md | 1 + cmd/zetae2e/local/local.go | 7 +- e2e/e2etests/e2etests.go | 72 ++- e2e/e2etests/test_bitcoin_deposit.go | 2 +- .../test_bitcoin_deposit_and_call_revert.go | 51 ++ e2e/e2etests/test_bitcoin_deposit_call.go | 4 +- e2e/e2etests/test_bitcoin_deposit_refund.go | 66 --- e2e/e2etests/test_bitcoin_donation.go | 44 ++ e2e/e2etests/test_bitcoin_std_deposit.go | 63 +++ .../test_bitcoin_std_deposit_and_call.go | 57 +++ ...est_bitcoin_std_deposit_and_call_revert.go | 53 +++ ...d_deposit_and_call_revert_other_address.go | 62 +++ e2e/e2etests/test_stress_btc_deposit.go | 2 +- e2e/runner/bitcoin.go | 73 ++- e2e/utils/zetacore.go | 25 +- pkg/memo/fields.go | 2 +- pkg/memo/fields_v0.go | 9 +- pkg/memo/fields_v0_test.go | 19 +- pkg/memo/memo.go | 40 +- pkg/memo/memo_test.go | 45 +- testutil/helpers.go | 10 + testutil/sample/crypto.go | 15 + x/crosschain/types/message_vote_inbound.go | 7 + .../types/message_vote_inbound_test.go | 39 ++ zetaclient/chains/bitcoin/observer/event.go | 253 ++++++++++ .../chains/bitcoin/observer/event_test.go | 447 ++++++++++++++++++ zetaclient/chains/bitcoin/observer/inbound.go | 124 ++--- .../chains/bitcoin/observer/inbound_test.go | 75 +++ zetaclient/chains/bitcoin/observer/witness.go | 2 +- zetaclient/chains/bitcoin/tx_script.go | 55 ++- zetaclient/chains/bitcoin/tx_script_test.go | 158 ++++--- zetaclient/chains/evm/observer/v2_inbound.go | 4 +- zetaclient/zetacore/constant.go | 3 + 33 files changed, 1570 insertions(+), 319 deletions(-) create mode 100644 e2e/e2etests/test_bitcoin_deposit_and_call_revert.go delete mode 100644 e2e/e2etests/test_bitcoin_deposit_refund.go create mode 100644 e2e/e2etests/test_bitcoin_donation.go create mode 100644 e2e/e2etests/test_bitcoin_std_deposit.go create mode 100644 e2e/e2etests/test_bitcoin_std_deposit_and_call.go create mode 100644 e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go create mode 100644 e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go create mode 100644 zetaclient/chains/bitcoin/observer/event.go create mode 100644 zetaclient/chains/bitcoin/observer/event_test.go diff --git a/changelog.md b/changelog.md index 37831d8857..da4d082c95 100644 --- a/changelog.md +++ b/changelog.md @@ -21,6 +21,7 @@ * [2987](https://github.com/zeta-chain/node/pull/2987) - add non-EVM standard inbound memo package * [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens * [3012](https://github.com/zeta-chain/node/pull/3012) - integrate authenticated calls erc20 smart contract functionality into protocol +* [3025](https://github.com/zeta-chain/node/pull/3025) - standard memo for Bitcoin inbound ### Refactor diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index de46edf450..4c6624451c 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -298,9 +298,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } bitcoinTests := []string{ + e2etests.TestBitcoinDonationName, e2etests.TestBitcoinDepositName, e2etests.TestBitcoinDepositAndCallName, - e2etests.TestBitcoinDepositRefundName, + e2etests.TestBitcoinDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoDepositName, + e2etests.TestBitcoinStdMemoDepositAndCallName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, e2etests.TestBitcoinWithdrawSegWitName, e2etests.TestBitcoinWithdrawInvalidAddressName, e2etests.TestZetaWithdrawBTCRevertName, diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 979876b352..98eac4397d 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -72,18 +72,23 @@ const ( Bitcoin tests Test transfer of Bitcoin asset across chains */ - TestBitcoinDepositName = "bitcoin_deposit" - TestBitcoinDepositRefundName = "bitcoin_deposit_refund" - TestBitcoinDepositAndCallName = "bitcoin_deposit_and_call" - TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" - TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" - TestBitcoinWithdrawMultipleName = "bitcoin_withdraw_multiple" - TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy" - TestBitcoinWithdrawP2WSHName = "bitcoin_withdraw_p2wsh" - TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh" - TestBitcoinWithdrawInvalidAddressName = "bitcoin_withdraw_invalid" - TestBitcoinWithdrawRestrictedName = "bitcoin_withdraw_restricted" - TestExtractBitcoinInscriptionMemoName = "bitcoin_memo_from_inscription" + TestBitcoinDepositName = "bitcoin_deposit" + TestBitcoinDepositAndCallName = "bitcoin_deposit_and_call" + TestBitcoinDepositAndCallRevertName = "bitcoin_deposit_and_call_revert" + TestBitcoinDonationName = "bitcoin_donation" + TestBitcoinStdMemoDepositName = "bitcoin_std_memo_deposit" + TestBitcoinStdMemoDepositAndCallName = "bitcoin_std_memo_deposit_and_call" + TestBitcoinStdMemoDepositAndCallRevertName = "bitcoin_std_memo_deposit_and_call_revert" + TestBitcoinStdMemoDepositAndCallRevertOtherAddressName = "bitcoin_std_memo_deposit_and_call_revert_other_address" + TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" + TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" + TestBitcoinWithdrawMultipleName = "bitcoin_withdraw_multiple" + TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy" + TestBitcoinWithdrawP2WSHName = "bitcoin_withdraw_p2wsh" + TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh" + TestBitcoinWithdrawInvalidAddressName = "bitcoin_withdraw_invalid" + TestBitcoinWithdrawRestrictedName = "bitcoin_withdraw_restricted" + TestExtractBitcoinInscriptionMemoName = "bitcoin_memo_from_inscription" /* Application tests @@ -466,6 +471,13 @@ var AllE2ETests = []runner.E2ETest{ /* Bitcoin tests */ + runner.NewE2ETest( + TestBitcoinDonationName, + "donate Bitcoin to TSS address", []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.1"}, + }, + TestBitcoinDonation, + ), runner.NewE2ETest( TestExtractBitcoinInscriptionMemoName, "extract memo from BTC inscription", []runner.ArgDefinition{ @@ -490,11 +502,43 @@ var AllE2ETests = []runner.E2ETest{ TestBitcoinDepositAndCall, ), runner.NewE2ETest( - TestBitcoinDepositRefundName, + TestBitcoinDepositAndCallRevertName, "deposit Bitcoin into ZEVM; expect refund", []runner.ArgDefinition{ {Description: "amount in btc", DefaultValue: "0.1"}, }, - TestBitcoinDepositRefund, + TestBitcoinDepositAndCallRevert, + ), + runner.NewE2ETest( + TestBitcoinStdMemoDepositName, + "deposit Bitcoin into ZEVM with standard memo", + []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.2"}, + }, + TestBitcoinStdMemoDeposit, + ), + runner.NewE2ETest( + TestBitcoinStdMemoDepositAndCallName, + "deposit Bitcoin into ZEVM and call a contract with standard memo", + []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.5"}, + }, + TestBitcoinStdMemoDepositAndCall, + ), + runner.NewE2ETest( + TestBitcoinStdMemoDepositAndCallRevertName, + "deposit Bitcoin into ZEVM and call a contract with standard memo; expect revert", + []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.1"}, + }, + TestBitcoinStdMemoDepositAndCallRevert, + ), + runner.NewE2ETest( + TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, + "deposit Bitcoin into ZEVM and call a contract with standard memo; expect revert to other address", + []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.1"}, + }, + TestBitcoinStdMemoDepositAndCallRevertOtherAddress, ), runner.NewE2ETest( TestBitcoinWithdrawSegWitName, diff --git a/e2e/e2etests/test_bitcoin_deposit.go b/e2e/e2etests/test_bitcoin_deposit.go index c9c6fdbf45..590a5c81d8 100644 --- a/e2e/e2etests/test_bitcoin_deposit.go +++ b/e2e/e2etests/test_bitcoin_deposit.go @@ -15,7 +15,7 @@ func TestBitcoinDeposit(r *runner.E2ERunner, args []string) { r.SetBtcAddress(r.Name, false) - txHash := r.DepositBTCWithAmount(depositAmount) + txHash := r.DepositBTCWithAmount(depositAmount, nil) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) diff --git a/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go b/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go new file mode 100644 index 0000000000..eed10485bf --- /dev/null +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go @@ -0,0 +1,51 @@ +package e2etests + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/testutil/sample" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +func TestBitcoinDepositAndCallRevert(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Given "Live" BTC network + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Given amount to send + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + amount += zetabitcoin.DefaultDepositorFee + + // Given a list of UTXOs + utxos, err := r.ListDeployerUTXOs() + require.NoError(r, err) + require.NotEmpty(r, utxos) + + // ACT + // Send BTC to TSS address with a dummy memo + // zetacore should revert cctx if call is made on a non-existing address + nonExistReceiver := sample.EthAddress() + badMemo := append(nonExistReceiver.Bytes(), []byte("gibberish-memo")...) + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, badMemo) + require.NoError(r, err) + require.NotEmpty(r, txHash) + + // ASSERT + // Now we want to make sure refund TX is completed. + cctx := utils.WaitCctxRevertedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + + // Check revert tx receiver address and amount + receiver, value := r.QueryOutboundReceiverAndAmount(cctx.OutboundParams[1].Hash) + assert.Equal(r, r.BTCDeployerAddress.EncodeAddress(), receiver) + assert.Positive(r, value) + + r.Logger.Info("Sent %f BTC to TSS with invalid memo, got refund of %d satoshis", amount, value) +} diff --git a/e2e/e2etests/test_bitcoin_deposit_call.go b/e2e/e2etests/test_bitcoin_deposit_call.go index c79ca9c1b8..d3d6917c59 100644 --- a/e2e/e2etests/test_bitcoin_deposit_call.go +++ b/e2e/e2etests/test_bitcoin_deposit_call.go @@ -49,7 +49,7 @@ func TestBitcoinDepositAndCall(r *runner.E2ERunner, args []string) { utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) // check if example contract has been called, 'bar' value should be set to amount - amoutSats, err := zetabitcoin.GetSatoshis(amount) + amountSats, err := zetabitcoin.GetSatoshis(amount) require.NoError(r, err) - utils.MustHaveCalledExampleContract(r, contract, big.NewInt(amoutSats)) + utils.MustHaveCalledExampleContract(r, contract, big.NewInt(amountSats)) } diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go deleted file mode 100644 index b0189d4b69..0000000000 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ /dev/null @@ -1,66 +0,0 @@ -package e2etests - -import ( - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/zeta-chain/node/e2e/runner" - "github.com/zeta-chain/node/e2e/utils" - "github.com/zeta-chain/node/x/crosschain/types" - zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" -) - -func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { - // ARRANGE - // Given BTC address - r.SetBtcAddress(r.Name, false) - - // Given "Live" BTC network - stop := r.MineBlocksIfLocalBitcoin() - defer stop() - - // Given amount to send - require.Len(r, args, 1) - amount := parseFloat(r, args[0]) - amount += zetabitcoin.DefaultDepositorFee - - // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) - - // ACT - // Send BTC to TSS address with a dummy memo - txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, []byte("gibberish-memo")) - require.NoError(r, err) - require.NotEmpty(r, txHash) - - // ASSERT - // Now we want to make sure refund TX is completed. - // Let's check that zetaclient issued a refund on BTC - searchForCrossChainWithBtcRefund := utils.Matches(func(tx types.CrossChainTx) bool { - return tx.GetCctxStatus().Status == types.CctxStatus_Reverted && - len(tx.OutboundParams) == 2 && - tx.OutboundParams[1].Hash != "" - }) - - cctxs := utils.WaitCctxByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient, searchForCrossChainWithBtcRefund) - require.Len(r, cctxs, 1) - - // Pick btc tx hash from the cctx - btcTxHash, err := chainhash.NewHashFromStr(cctxs[0].OutboundParams[1].Hash) - require.NoError(r, err) - - // Query the BTC network to check the refund transaction - refundTx, err := r.BtcRPCClient.GetTransaction(btcTxHash) - require.NoError(r, err, refundTx) - - // Finally, check the refund transaction details - refundTxDetails := refundTx.Details[0] - assert.Equal(r, "receive", refundTxDetails.Category) - assert.Equal(r, r.BTCDeployerAddress.EncodeAddress(), refundTxDetails.Address) - assert.NotEmpty(r, refundTxDetails.Amount) - - r.Logger.Info("Sent %f BTC to TSS with invalid memo, got refund of %f BTC", amount, refundTxDetails.Amount) -} diff --git a/e2e/e2etests/test_bitcoin_donation.go b/e2e/e2etests/test_bitcoin_donation.go new file mode 100644 index 0000000000..1dd5a34859 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_donation.go @@ -0,0 +1,44 @@ +package e2etests + +import ( + "time" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/pkg/constant" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +func TestBitcoinDonation(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Given "Live" BTC network + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Given amount to send + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + amountTotal := amount + zetabitcoin.DefaultDepositorFee + + // Given a list of UTXOs + utxos, err := r.ListDeployerUTXOs() + require.NoError(r, err) + require.NotEmpty(r, utxos) + + // ACT + // Send BTC to TSS address with donation message + memo := []byte(constant.DonationMessage) + txHash, err := r.SendToTSSFromDeployerWithMemo(amountTotal, utxos, memo) + require.NoError(r, err) + + // ASSERT after 4 Zeta blocks + time.Sleep(constant.ZetaBlockTime * 4) + req := &crosschaintypes.QueryInboundHashToCctxDataRequest{InboundHash: txHash.String()} + _, err = r.CctxClient.InTxHashToCctxData(r.Ctx, req) + require.Error(r, err) +} diff --git a/e2e/e2etests/test_bitcoin_std_deposit.go b/e2e/e2etests/test_bitcoin_std_deposit.go new file mode 100644 index 0000000000..fa123b94cd --- /dev/null +++ b/e2e/e2etests/test_bitcoin_std_deposit.go @@ -0,0 +1,63 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/memo" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +func TestBitcoinStdMemoDeposit(r *runner.E2ERunner, args []string) { + // setup deployer BTC address + r.SetBtcAddress(r.Name, false) + + // start mining blocks if local bitcoin + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // parse amount to deposit + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + + // get ERC20 BTC balance before deposit + balanceBefore, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + r.Logger.Info("runner balance of BTC before deposit: %d satoshis", balanceBefore) + + // create standard memo with receiver address + memo := &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDeposit, + }, + FieldsV0: memo.FieldsV0{ + Receiver: r.EVMAddress(), // to deployer self + }, + } + + // deposit BTC with standard memo + txHash := r.DepositBTCWithAmount(amount, memo) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "bitcoin_std_memo_deposit") + utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // get ERC20 BTC balance after deposit + balanceAfter, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + r.Logger.Info("runner balance of BTC after deposit: %d satoshis", balanceAfter) + + // the runner balance should be increased by the deposit amount + amountIncreased := new(big.Int).Sub(balanceAfter, balanceBefore) + amountSatoshis, err := bitcoin.GetSatoshis(amount) + require.NoError(r, err) + require.Equal(r, uint64(amountSatoshis), amountIncreased.Uint64()) +} diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call.go new file mode 100644 index 0000000000..7a9c6ca255 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call.go @@ -0,0 +1,57 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/memo" + testcontract "github.com/zeta-chain/node/testutil/contracts" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +func TestBitcoinStdMemoDepositAndCall(r *runner.E2ERunner, args []string) { + // setup deployer BTC address + r.SetBtcAddress(r.Name, false) + + // start mining blocks if local bitcoin + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // parse amount to deposit + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + + // deploy an example contract in ZEVM + contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + + // create standard memo with [receiver, payload] + memo := &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDepositAndCall, + }, + FieldsV0: memo.FieldsV0{ + Receiver: contractAddr, + Payload: []byte("hello satoshi"), + }, + } + + // deposit BTC with standard memo + txHash := r.DepositBTCWithAmount(amount, memo) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "bitcoin_std_memo_deposit_and_call") + utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // check if example contract has been called, 'bar' value should be set to amount + amountSats, err := zetabitcoin.GetSatoshis(amount) + require.NoError(r, err) + utils.MustHaveCalledExampleContract(r, contract, big.NewInt(amountSats)) +} diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go new file mode 100644 index 0000000000..76bf128aad --- /dev/null +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go @@ -0,0 +1,53 @@ +package e2etests + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/memo" + "github.com/zeta-chain/node/testutil/sample" +) + +func TestBitcoinStdMemoDepositAndCallRevert(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Start mining blocks + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Parse amount to send + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + + // Create a memo to call non-existing contract + memo := &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDepositAndCall, + }, + FieldsV0: memo.FieldsV0{ + Receiver: sample.EthAddress(), // non-existing contract + Payload: []byte("a payload"), + }, + } + + // ACT + // Deposit + txHash := r.DepositBTCWithAmount(amount, memo) + + // ASSERT + // Now we want to make sure revert TX is completed. + cctx := utils.WaitCctxRevertedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + + // Check revert tx receiver address and amount + receiver, value := r.QueryOutboundReceiverAndAmount(cctx.OutboundParams[1].Hash) + assert.Equal(r, r.BTCDeployerAddress.EncodeAddress(), receiver) + assert.Positive(r, value) + + r.Logger.Info("Sent %f BTC to TSS to call non-existing contract, got refund of %d satoshis", amount, value) +} diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go new file mode 100644 index 0000000000..c6da1b1696 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go @@ -0,0 +1,62 @@ +package e2etests + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/memo" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestBitcoinStdMemoDepositAndCallRevertOtherAddress(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Start mining blocks + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Parse amount to send + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + + // Create a memo to call non-existing contract + revertAddress := "bcrt1qy9pqmk2pd9sv63g27jt8r657wy0d9uee4x2dt2" + memo := &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDepositAndCall, + }, + FieldsV0: memo.FieldsV0{ + Receiver: sample.EthAddress(), // non-existing contract + Payload: []byte("a payload"), + RevertOptions: types.RevertOptions{ + RevertAddress: revertAddress, + }, + }, + } + + // ACT + // Deposit + txHash := r.DepositBTCWithAmount(amount, memo) + + // ASSERT + // Now we want to make sure revert TX is completed. + cctx := utils.WaitCctxRevertedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + + // Check revert tx receiver address and amount + receiver, value := r.QueryOutboundReceiverAndAmount(cctx.OutboundParams[1].Hash) + assert.Equal(r, revertAddress, receiver) + assert.Positive(r, value) + + r.Logger.Info( + "Sent %f BTC to TSS to call non-existing contract, got refund of %d satoshis to other address", + amount, + value, + ) +} diff --git a/e2e/e2etests/test_stress_btc_deposit.go b/e2e/e2etests/test_stress_btc_deposit.go index 53caea09df..bedf004bdf 100644 --- a/e2e/e2etests/test_stress_btc_deposit.go +++ b/e2e/e2etests/test_stress_btc_deposit.go @@ -30,7 +30,7 @@ func TestStressBTCDeposit(r *runner.E2ERunner, args []string) { // send the deposits for i := 0; i < numDeposits; i++ { i := i - txHash := r.DepositBTCWithAmount(depositAmount) + txHash := r.DepositBTCWithAmount(depositAmount, nil) r.Logger.Print("index %d: starting deposit, tx hash: %s", i, txHash.String()) eg.Go(func() error { return monitorBTCDeposit(r, txHash, i, time.Now()) }) diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index d2c04fccf0..3d65589fa5 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -21,6 +21,7 @@ import ( "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/pkg/memo" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" @@ -76,10 +77,8 @@ func (r *E2ERunner) GetTop20UTXOsForTssAddress() ([]btcjson.ListUnspentResult, e return utxos, nil } -// DepositBTCWithAmount deposits BTC on ZetaChain with a specific amount -func (r *E2ERunner) DepositBTCWithAmount(amount float64) *chainhash.Hash { - r.Logger.Print("⏳ depositing BTC into ZEVM") - +// DepositBTCWithAmount deposits BTC into ZetaChain with a specific amount and memo +func (r *E2ERunner) DepositBTCWithAmount(amount float64, memo *memo.InboundMemo) *chainhash.Hash { // list deployer utxos utxos, err := r.ListDeployerUTXOs() require.NoError(r, err) @@ -100,8 +99,16 @@ func (r *E2ERunner) DepositBTCWithAmount(amount float64) *chainhash.Hash { r.Logger.Info(" spendableUTXOs: %d", spendableUTXOs) r.Logger.Info("Now sending two txs to TSS address...") + // add depositor fee so that receiver gets the exact given 'amount' in ZetaChain amount += zetabitcoin.DefaultDepositorFee - txHash, err := r.SendToTSSFromDeployerToDeposit(amount, utxos) + + // deposit to TSS address + var txHash *chainhash.Hash + if memo != nil { + txHash, err = r.DepositBTCWithStandardMemo(amount, utxos, memo) + } else { + txHash, err = r.DepositBTCWithLegacyMemo(amount, utxos) + } require.NoError(r, err) r.Logger.Info("send BTC to TSS txHash: %s", txHash.String()) @@ -140,11 +147,11 @@ func (r *E2ERunner) DepositBTC() { // send two transactions to the TSS address amount1 := 1.1 + zetabitcoin.DefaultDepositorFee - _, err = r.SendToTSSFromDeployerToDeposit(amount1, utxos[:2]) + _, err = r.DepositBTCWithLegacyMemo(amount1, utxos[:2]) require.NoError(r, err) amount2 := 0.05 + zetabitcoin.DefaultDepositorFee - txHash2, err := r.SendToTSSFromDeployerToDeposit(amount2, utxos[2:4]) + txHash2, err := r.DepositBTCWithLegacyMemo(amount2, utxos[2:4]) require.NoError(r, err) // send a donation to the TSS address to compensate for the funds minted automatically during pool creation @@ -168,11 +175,34 @@ func (r *E2ERunner) DepositBTC() { require.Equal(r, 1, balance.Sign(), "balance should be positive") } -func (r *E2ERunner) SendToTSSFromDeployerToDeposit(amount float64, inputUTXOs []btcjson.ListUnspentResult) ( - *chainhash.Hash, - error, -) { - return r.SendToTSSFromDeployerWithMemo(amount, inputUTXOs, r.EVMAddress().Bytes()) +// DepositBTCWithLegacyMemo deposits BTC from the deployer address to the TSS using legacy memo +// +// The legacy memo layout: [20-byte receiver] + [payload] +func (r *E2ERunner) DepositBTCWithLegacyMemo( + amount float64, + inputUTXOs []btcjson.ListUnspentResult, +) (*chainhash.Hash, error) { + r.Logger.Info("⏳ depositing BTC into ZEVM with legacy memo") + + // payload is not needed for pure deposit + memoBytes := r.EVMAddress().Bytes() + + return r.SendToTSSFromDeployerWithMemo(amount, inputUTXOs, memoBytes) +} + +// DepositBTCWithStandardMemo deposits BTC from the deployer address to the TSS using standard `InboundMemo` struct +func (r *E2ERunner) DepositBTCWithStandardMemo( + amount float64, + inputUTXOs []btcjson.ListUnspentResult, + memoStd *memo.InboundMemo, +) (*chainhash.Hash, error) { + r.Logger.Info("⏳ depositing BTC into ZEVM with standard memo") + + // encode memo to bytes + memoBytes, err := memoStd.EncodeToBytes() + require.NoError(r, err) + + return r.SendToTSSFromDeployerWithMemo(amount, inputUTXOs, memoBytes) } func (r *E2ERunner) SendToTSSFromDeployerWithMemo( @@ -366,6 +396,25 @@ func (r *E2ERunner) GenerateToAddressIfLocalBitcoin( return nil, nil } +// QueryOutboundReceiverAndAmount queries the outbound receiver and amount (in satoshis) from the given txid +func (r *E2ERunner) QueryOutboundReceiverAndAmount(txid string) (string, int64) { + txHash, err := chainhash.NewHashFromStr(txid) + require.NoError(r, err) + + // query outbound raw transaction + revertTx, err := r.BtcRPCClient.GetRawTransaction(txHash) + require.NoError(r, err, revertTx) + require.True(r, len(revertTx.MsgTx().TxOut) >= 2, "bitcoin outbound must have at least two outputs") + + // parse receiver address from pkScript + txOutput := revertTx.MsgTx().TxOut[1] + pkScript := txOutput.PkScript + receiver, err := zetabitcoin.DecodeScriptP2WPKH(hex.EncodeToString(pkScript), r.BitcoinParams) + require.NoError(r, err) + + return receiver, txOutput.Value +} + // MineBlocksIfLocalBitcoin mines blocks on the local BTC chain at a rate of 1 blocks every 5 seconds // and returns a channel that can be used to stop the mining // If the chain is not local, the function does nothing diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 6d50be10da..33f5d68262 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -187,13 +187,22 @@ func WaitCCTXMinedByIndex( type WaitOpts func(c *waitConfig) -// MatchStatus waits for a specific CCTX status. +// MatchStatus is the WaitOpts that matches CCTX with the given status. func MatchStatus(s crosschaintypes.CctxStatus) WaitOpts { return Matches(func(tx crosschaintypes.CrossChainTx) bool { return tx.CctxStatus != nil && tx.CctxStatus.Status == s }) } +// MatchReverted is the WaitOpts that matches reverted CCTX. +func MatchReverted() WaitOpts { + return Matches(func(tx crosschaintypes.CrossChainTx) bool { + return tx.GetCctxStatus().Status == crosschaintypes.CctxStatus_Reverted && + len(tx.OutboundParams) == 2 && + tx.OutboundParams[1].Hash != "" + }) +} + // Matches adds a filter to WaitCctxByInboundHash that checks cctxs match provided callback. // ALL cctxs should match this filter. func Matches(fn func(tx crosschaintypes.CrossChainTx) bool) WaitOpts { @@ -204,6 +213,20 @@ type waitConfig struct { matchFunction func(tx crosschaintypes.CrossChainTx) bool } +// WaitCctxRevertedByInboundHash waits until cctx is reverted by inbound hash. +func WaitCctxRevertedByInboundHash( + ctx context.Context, + t require.TestingT, + hash string, + c CCTXClient, +) crosschaintypes.CrossChainTx { + // wait for cctx to be reverted + cctxs := WaitCctxByInboundHash(ctx, t, hash, c, MatchReverted()) + require.Len(t, cctxs, 1) + + return cctxs[0] +} + // WaitCctxByInboundHash waits until cctx appears by inbound hash. func WaitCctxByInboundHash( ctx context.Context, diff --git a/pkg/memo/fields.go b/pkg/memo/fields.go index fff853f955..e0415e0636 100644 --- a/pkg/memo/fields.go +++ b/pkg/memo/fields.go @@ -6,7 +6,7 @@ type Fields interface { Pack(opCode OpCode, encodingFmt EncodingFormat, dataFlags uint8) ([]byte, error) // Unpack decodes the memo fields - Unpack(opCode OpCode, encodingFmt EncodingFormat, dataFlags uint8, data []byte) error + Unpack(encodingFmt EncodingFormat, dataFlags uint8, data []byte) error // Validate checks if the fields are valid Validate(opCode OpCode, dataFlags uint8) error diff --git a/pkg/memo/fields_v0.go b/pkg/memo/fields_v0.go index a8f79d99ba..d30745b6a3 100644 --- a/pkg/memo/fields_v0.go +++ b/pkg/memo/fields_v0.go @@ -54,18 +54,13 @@ func (f *FieldsV0) Pack(opCode OpCode, encodingFmt EncodingFormat, dataFlags uin } // Unpack decodes the memo fields -func (f *FieldsV0) Unpack(opCode OpCode, encodingFmt EncodingFormat, dataFlags uint8, data []byte) error { +func (f *FieldsV0) Unpack(encodingFmt EncodingFormat, dataFlags uint8, data []byte) error { codec, err := GetCodec(encodingFmt) if err != nil { return errors.Wrap(err, "unable to get codec") } - err = f.unpackFields(codec, dataFlags, data) - if err != nil { - return err - } - - return f.Validate(opCode, dataFlags) + return f.unpackFields(codec, dataFlags, data) } // Validate checks if the fields are valid diff --git a/pkg/memo/fields_v0_test.go b/pkg/memo/fields_v0_test.go index 13742422c2..11cff3e1bd 100644 --- a/pkg/memo/fields_v0_test.go +++ b/pkg/memo/fields_v0_test.go @@ -125,7 +125,6 @@ func Test_V0_Unpack(t *testing.T) { tests := []struct { name string - opCode memo.OpCode encodeFmt memo.EncodingFormat dataFlags byte data []byte @@ -134,7 +133,6 @@ func Test_V0_Unpack(t *testing.T) { }{ { name: "unpack all fields with ABI encoding", - opCode: memo.OpCodeDepositAndCall, encodeFmt: memo.EncodingFmtABI, dataFlags: flagsAllFieldsSet, // all fields are set data: ABIPack(t, @@ -156,7 +154,6 @@ func Test_V0_Unpack(t *testing.T) { }, { name: "unpack all fields with compact encoding", - opCode: memo.OpCodeDepositAndCall, encodeFmt: memo.EncodingFmtCompactShort, dataFlags: flagsAllFieldsSet, // all fields are set data: CompactPack( @@ -179,7 +176,6 @@ func Test_V0_Unpack(t *testing.T) { }, { name: "unpack empty ABI encoded payload if flag is set", - opCode: memo.OpCodeDepositAndCall, encodeFmt: memo.EncodingFmtABI, dataFlags: 0b00000010, // payload flags are set data: ABIPack(t, @@ -188,7 +184,6 @@ func Test_V0_Unpack(t *testing.T) { }, { name: "unpack empty compact encoded payload if flag is set", - opCode: memo.OpCodeDepositAndCall, encodeFmt: memo.EncodingFmtCompactShort, dataFlags: 0b00000010, // payload flag is set data: CompactPack( @@ -198,7 +193,6 @@ func Test_V0_Unpack(t *testing.T) { }, { name: "unable to get codec on invalid encoding format", - opCode: memo.OpCodeDepositAndCall, encodeFmt: 0x0F, dataFlags: 0b00000001, data: []byte{}, @@ -206,7 +200,6 @@ func Test_V0_Unpack(t *testing.T) { }, { name: "failed to unpack ABI encoded data with compact encoding format", - opCode: memo.OpCodeDepositAndCall, encodeFmt: memo.EncodingFmtCompactShort, dataFlags: 0b00000011, // receiver and payload flags are set data: ABIPack(t, @@ -214,23 +207,13 @@ func Test_V0_Unpack(t *testing.T) { memo.ArgPayload(fBytes)), errMsg: "failed to unpack arguments", }, - { - name: "fields validation failed due to empty receiver address", - opCode: memo.OpCodeDepositAndCall, - encodeFmt: memo.EncodingFmtABI, - dataFlags: 0b00000011, // receiver and payload flags are set - data: ABIPack(t, - memo.ArgReceiver(common.Address{}), - memo.ArgPayload(fBytes)), - errMsg: "receiver address is empty", - }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // unpack the fields fields := memo.FieldsV0{} - err := fields.Unpack(tc.opCode, tc.encodeFmt, tc.dataFlags, tc.data) + err := fields.Unpack(tc.encodeFmt, tc.dataFlags, tc.data) // validate the error message if tc.errMsg != "" { diff --git a/pkg/memo/memo.go b/pkg/memo/memo.go index 30670952b0..ca429a5f38 100644 --- a/pkg/memo/memo.go +++ b/pkg/memo/memo.go @@ -8,6 +8,11 @@ import ( "github.com/pkg/errors" ) +const ( + // version0 is the latest version of the memo + version0 uint8 = 0 +) + // InboundMemo represents the inbound memo structure for non-EVM chains type InboundMemo struct { // Header contains the memo header @@ -37,7 +42,7 @@ func (m *InboundMemo) EncodeToBytes() ([]byte, error) { // encode fields based on version var data []byte switch m.Version { - case 0: + case version0: data, err = m.FieldsV0.Pack(m.OpCode, m.EncodingFmt, dataFlags) default: return nil, fmt.Errorf("invalid memo version: %d", m.Version) @@ -51,28 +56,41 @@ func (m *InboundMemo) EncodeToBytes() ([]byte, error) { // DecodeFromBytes decodes a InboundMemo struct from raw bytes // -// Returns an error if given data is not a valid memo -func DecodeFromBytes(data []byte) (*InboundMemo, error) { +// Returns: +// - [memo, true, nil] if given data is successfully decoded as a memo. +// - [nil, true, err] if given data is successfully decoded as a memo but contains improper field values. +// - [nil, false, err] if given data can't be decoded as a memo. +// +// Note: we won't have to differentiate between the two 'true' cases if legacy memo phase out is completed. +func DecodeFromBytes(data []byte) (*InboundMemo, bool, error) { memo := &InboundMemo{} // decode header err := memo.Header.DecodeFromBytes(data) if err != nil { - return nil, errors.Wrap(err, "failed to decode memo header") + return nil, false, errors.Wrap(err, "failed to decode memo header") } // decode fields based on version switch memo.Version { - case 0: - err = memo.FieldsV0.Unpack(memo.OpCode, memo.EncodingFmt, memo.Header.DataFlags, data[HeaderSize:]) + case version0: + // unpack fields + err = memo.FieldsV0.Unpack(memo.EncodingFmt, memo.Header.DataFlags, data[HeaderSize:]) + if err != nil { + return nil, false, errors.Wrap(err, "failed to unpack memo FieldsV0") + } + + // validate fields + // Note: a well-formatted memo may still contain improper field values + err = memo.FieldsV0.Validate(memo.OpCode, memo.Header.DataFlags) + if err != nil { + return nil, true, errors.Wrap(err, "failed to validate memo FieldsV0") + } default: - return nil, fmt.Errorf("invalid memo version: %d", memo.Version) - } - if err != nil { - return nil, errors.Wrapf(err, "failed to unpack memo fields version: %d", memo.Version) + return nil, false, fmt.Errorf("invalid memo version: %d", memo.Version) } - return memo, nil + return memo, true, nil } // DecodeLegacyMemoHex decodes hex encoded memo message into address and calldata diff --git a/pkg/memo/memo_test.go b/pkg/memo/memo_test.go index e6cb067793..4eabd6a18f 100644 --- a/pkg/memo/memo_test.go +++ b/pkg/memo/memo_test.go @@ -137,7 +137,8 @@ func Test_Memo_EncodeToBytes(t *testing.T) { require.Equal(t, append(tt.expectedHead, tt.expectedData...), data) // decode the memo and compare with the original - decodedMemo, err := memo.DecodeFromBytes(data) + decodedMemo, isMemo, err := memo.DecodeFromBytes(data) + require.True(t, isMemo) require.NoError(t, err) require.Equal(t, tt.memo, decodedMemo) }) @@ -154,6 +155,7 @@ func Test_Memo_DecodeFromBytes(t *testing.T) { name string head []byte data []byte + isMemo bool expectedMemo memo.InboundMemo errMsg string }{ @@ -172,6 +174,7 @@ func Test_Memo_DecodeFromBytes(t *testing.T) { memo.ArgRevertAddress(fString), memo.ArgAbortAddress(fAddress), memo.ArgRevertMessage(fBytes)), + isMemo: true, expectedMemo: memo.InboundMemo{ Header: memo.Header{ Version: 0, @@ -207,6 +210,7 @@ func Test_Memo_DecodeFromBytes(t *testing.T) { memo.ArgRevertAddress(fString), memo.ArgAbortAddress(fAddress), memo.ArgRevertMessage(fBytes)), + isMemo: true, expectedMemo: memo.InboundMemo{ Header: memo.Header{ Version: 0, @@ -251,20 +255,51 @@ func Test_Memo_DecodeFromBytes(t *testing.T) { memo.EncodingFmtCompactShort, memo.ArgReceiver(fAddress), ), // but data is compact encoded - errMsg: "failed to unpack memo fields", + errMsg: "failed to unpack memo FieldsV0", + }, + { + name: "should return [nil, true, err] if fields validation fails", + head: MakeHead( + 0, + uint8(memo.EncodingFmtABI), + uint8(memo.OpCodeDepositAndCall), + 0, + 0b00000011, // receiver flag is set + ), + data: ABIPack(t, + memo.ArgReceiver(common.Address{}), // empty receiver address provided + memo.ArgPayload(fBytes)), + isMemo: true, // it's still a memo, but with invalid field values + errMsg: "failed to validate memo FieldsV0", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { data := append(tt.head, tt.data...) - memo, err := memo.DecodeFromBytes(data) + memo, isMemo, err := memo.DecodeFromBytes(data) + + // check error message if tt.errMsg != "" { + require.Nil(t, memo) require.ErrorContains(t, err, tt.errMsg) return } - require.NoError(t, err) - require.Equal(t, tt.expectedMemo, *memo) + + // a standard memo or not + require.Equal(t, tt.isMemo, isMemo) + if !isMemo { + require.Nil(t, memo) + return + } + + // if it's a standard memo, depending on validation result + if err != nil { + require.Nil(t, memo) + } else { + require.NotNil(t, memo) + require.Equal(t, tt.expectedMemo, *memo) + } }) } } diff --git a/testutil/helpers.go b/testutil/helpers.go index dc49a0b024..f39c4931fb 100644 --- a/testutil/helpers.go +++ b/testutil/helpers.go @@ -1,11 +1,14 @@ package testutil import ( + "encoding/hex" "fmt" "os" "strings" + "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const helpersFile = "testutil/helpers.go" @@ -36,3 +39,10 @@ func exit(err error) { os.Exit(1) } + +// HexToBytes convert hex string to bytes +func HexToBytes(t *testing.T, hexStr string) []byte { + bytes, err := hex.DecodeString(hexStr) + require.NoError(t, err) + return bytes +} diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index e14b64f967..144b7d8e68 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -7,6 +7,9 @@ import ( "strconv" "testing" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/cometbft/cometbft/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" @@ -57,6 +60,18 @@ func EthAddress() ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).Bytes()) } +// BtcAddressP2WPKH returns a sample btc P2WPKH address +func BtcAddressP2WPKH(t *testing.T, net *chaincfg.Params) string { + privateKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + pubKeyHash := btcutil.Hash160(privateKey.PubKey().SerializeCompressed()) + addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, net) + require.NoError(t, err) + + return addr.String() +} + // SolanaPrivateKey returns a sample solana private key func SolanaPrivateKey(t *testing.T) solana.PrivateKey { privKey, err := solana.NewRandomPrivateKey() diff --git a/x/crosschain/types/message_vote_inbound.go b/x/crosschain/types/message_vote_inbound.go index 34afb68be2..3db9fdde0f 100644 --- a/x/crosschain/types/message_vote_inbound.go +++ b/x/crosschain/types/message_vote_inbound.go @@ -22,6 +22,13 @@ const MaxMessageLength = 10240 // InboundVoteOption is a function that sets some option on the inbound vote message type InboundVoteOption func(*MsgVoteInbound) +// WithMemoRevertOptions sets the revert options for inbound vote message +func WithRevertOptions(revertOptions RevertOptions) InboundVoteOption { + return func(msg *MsgVoteInbound) { + msg.RevertOptions = revertOptions + } +} + // 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 { diff --git a/x/crosschain/types/message_vote_inbound_test.go b/x/crosschain/types/message_vote_inbound_test.go index e3e9bc7fce..2c30b2a343 100644 --- a/x/crosschain/types/message_vote_inbound_test.go +++ b/x/crosschain/types/message_vote_inbound_test.go @@ -42,6 +42,45 @@ func TestNewMsgVoteInbound(t *testing.T) { require.EqualValues(t, types.NewEmptyRevertOptions(), msg.RevertOptions) }) + t.Run("can set revert options", func(t *testing.T) { + revertAddress := sample.EthAddress() + abortAddress := sample.EthAddress() + revertMessage := sample.Bytes() + + msg := types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 31, + sample.String(), + sample.String(), + 31, + math.NewUint(31), + sample.String(), + sample.String(), + 31, + 31, + coin.CoinType_Gas, + sample.String(), + 31, + types.ProtocolContractVersion_V2, + true, + types.WithRevertOptions(types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.NewUint(21000), + }), + ) + require.EqualValues(t, types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.NewUint(21000), + }, msg.RevertOptions) + }) + t.Run("can set ZEVM revert options", func(t *testing.T) { revertAddress := sample.EthAddress() abortAddress := sample.EthAddress() diff --git a/zetaclient/chains/bitcoin/observer/event.go b/zetaclient/chains/bitcoin/observer/event.go new file mode 100644 index 0000000000..69657d29f1 --- /dev/null +++ b/zetaclient/chains/bitcoin/observer/event.go @@ -0,0 +1,253 @@ +package observer + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + + cosmosmath "cosmossdk.io/math" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/pkg/crypto" + "github.com/zeta-chain/node/pkg/memo" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/compliance" + "github.com/zeta-chain/node/zetaclient/config" + "github.com/zeta-chain/node/zetaclient/logs" +) + +// InboundProcessability is an enum representing the processability of an inbound +type InboundProcessability int + +const ( + // InboundProcessabilityGood represents a processable inbound + InboundProcessabilityGood InboundProcessability = iota + + // InboundProcessabilityDonation represents a donation inbound + InboundProcessabilityDonation + + // InboundProcessabilityComplianceViolation represents a compliance violation + InboundProcessabilityComplianceViolation +) + +// BTCInboundEvent represents an incoming transaction event +type BTCInboundEvent struct { + // FromAddress is the first input address + FromAddress string + + // ToAddress is the ZEVM receiver address + ToAddress string + + // Value is the amount of BTC + Value float64 + + // DepositorFee is the deposit fee + DepositorFee float64 + + // MemoBytes is the memo of inbound + MemoBytes []byte + + // MemoStd is the standard inbound memo if it can be decoded + MemoStd *memo.InboundMemo + + // BlockNumber is the block number of the inbound + BlockNumber uint64 + + // TxHash is the hash of the inbound + TxHash string +} + +// Processability returns the processability of the inbound event +func (event *BTCInboundEvent) Processability() InboundProcessability { + // compliance check on sender and receiver addresses + if config.ContainRestrictedAddress(event.FromAddress, event.ToAddress) { + return InboundProcessabilityComplianceViolation + } + + // compliance check on receiver, revert/abort addresses in standard memo + if event.MemoStd != nil { + if config.ContainRestrictedAddress( + event.MemoStd.Receiver.Hex(), + event.MemoStd.RevertOptions.RevertAddress, + event.MemoStd.RevertOptions.AbortAddress, + ) { + return InboundProcessabilityComplianceViolation + } + } + + // donation check + if bytes.Equal(event.MemoBytes, []byte(constant.DonationMessage)) { + return InboundProcessabilityDonation + } + + return InboundProcessabilityGood +} + +// DecodeMemoBytes decodes the contained memo bytes as either standard or legacy memo +func (event *BTCInboundEvent) DecodeMemoBytes(chainID int64) error { + var ( + err error + isStandardMemo bool + memoStd *memo.InboundMemo + receiver ethcommon.Address + ) + + // skip decoding donation tx as it won't go through zetacore + if bytes.Equal(event.MemoBytes, []byte(constant.DonationMessage)) { + return nil + } + + // try to decode the standard memo as the preferred format + // the standard memo is NOT enabled for Bitcoin mainnet + + if chainID != chains.BitcoinMainnet.ChainId { + memoStd, isStandardMemo, err = memo.DecodeFromBytes(event.MemoBytes) + } + + // process standard memo or fallback to legacy memo + if isStandardMemo { + // skip standard memo that carries improper data + if err != nil { + return errors.Wrap(err, "standard memo contains improper data") + } + + // validate the content of the standard memo + err = ValidateStandardMemo(*memoStd, chainID) + if err != nil { + return errors.Wrap(err, "invalid standard memo for bitcoin") + } + + event.MemoStd = memoStd + receiver = memoStd.Receiver + } else { + parsedAddress, _, err := memo.DecodeLegacyMemoHex(hex.EncodeToString(event.MemoBytes)) + if err != nil { // unreachable code + return errors.Wrap(err, "invalid legacy memo") + } + receiver = parsedAddress + } + + // ensure the receiver is valid + if crypto.IsEmptyAddress(receiver) { + return errors.New("got empty receiver address from memo") + } + event.ToAddress = receiver.Hex() + + return nil +} + +// ValidateStandardMemo validates the standard memo in Bitcoin context +func ValidateStandardMemo(memoStd memo.InboundMemo, chainID int64) error { + // NoAssetCall will be disabled for Bitcoin until full V2 support + // https://github.com/zeta-chain/node/issues/2711 + if memoStd.OpCode == memo.OpCodeCall { + return errors.New("NoAssetCall is disabled for Bitcoin") + } + + // ensure the revert address is a valid and supported BTC address + revertAddress := memoStd.RevertOptions.RevertAddress + if revertAddress != "" { + btcAddress, err := chains.DecodeBtcAddress(revertAddress, chainID) + if err != nil { + return errors.Wrapf(err, "invalid revert address in memo: %s", revertAddress) + } + if !chains.IsBtcAddressSupported(btcAddress) { + return fmt.Errorf("unsupported revert address in memo: %s", revertAddress) + } + } + + return nil +} + +// CheckEventProcessability checks if the inbound event is processable +func (ob *Observer) CheckEventProcessability(event BTCInboundEvent) bool { + // check if the event is processable + switch result := event.Processability(); result { + case InboundProcessabilityGood: + return true + case InboundProcessabilityDonation: + logFields := map[string]any{ + logs.FieldChain: ob.Chain().ChainId, + logs.FieldTx: event.TxHash, + } + ob.Logger().Inbound.Info().Fields(logFields).Msgf("thank you rich folk for your donation!") + return false + case InboundProcessabilityComplianceViolation: + compliance.PrintComplianceLog(ob.logger.Inbound, ob.logger.Compliance, + false, ob.Chain().ChainId, event.TxHash, event.FromAddress, event.ToAddress, "BTC") + return false + default: + ob.Logger().Inbound.Error().Msgf("unreachable code got InboundProcessability: %v", result) + return false + } +} + +// NewInboundVoteFromLegacyMemo creates a MsgVoteInbound message for inbound that uses legacy memo +func (ob *Observer) NewInboundVoteFromLegacyMemo( + event *BTCInboundEvent, + amountSats *big.Int, +) *crosschaintypes.MsgVoteInbound { + message := hex.EncodeToString(event.MemoBytes) + + return crosschaintypes.NewMsgVoteInbound( + ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), + event.FromAddress, + ob.Chain().ChainId, + event.FromAddress, + event.ToAddress, + ob.ZetacoreClient().Chain().ChainId, + cosmosmath.NewUintFromBigInt(amountSats), + message, + event.TxHash, + event.BlockNumber, + 0, + coin.CoinType_Gas, + "", + 0, + crosschaintypes.ProtocolContractVersion_V1, + false, // not relevant for v1 + ) +} + +// NewInboundVoteFromStdMemo creates a MsgVoteInbound message for inbound that uses standard memo +// TODO: upgrade to ProtocolContractVersion_V2 and enable more options +// https://github.com/zeta-chain/node/issues/2711 +func (ob *Observer) NewInboundVoteFromStdMemo( + event *BTCInboundEvent, + amountSats *big.Int, +) *crosschaintypes.MsgVoteInbound { + // replace 'sender' with 'revertAddress' if specified in the memo, so that + // zetacore will refund to the address specified by the user in the revert options. + sender := event.FromAddress + if event.MemoStd.RevertOptions.RevertAddress != "" { + sender = event.MemoStd.RevertOptions.RevertAddress + } + + // make a legacy message so that zetacore can process it as V1 + msgBytes := append(event.MemoStd.Receiver.Bytes(), event.MemoStd.Payload...) + message := hex.EncodeToString(msgBytes) + + return crosschaintypes.NewMsgVoteInbound( + ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), + sender, + ob.Chain().ChainId, + event.FromAddress, + event.ToAddress, + ob.ZetacoreClient().Chain().ChainId, + cosmosmath.NewUintFromBigInt(amountSats), + message, + event.TxHash, + event.BlockNumber, + 0, + coin.CoinType_Gas, + "", + 0, + crosschaintypes.ProtocolContractVersion_V1, + false, // not relevant for v1 + ) +} diff --git a/zetaclient/chains/bitcoin/observer/event_test.go b/zetaclient/chains/bitcoin/observer/event_test.go new file mode 100644 index 0000000000..5ed8e9b103 --- /dev/null +++ b/zetaclient/chains/bitcoin/observer/event_test.go @@ -0,0 +1,447 @@ +package observer_test + +import ( + "encoding/hex" + "math/big" + "testing" + + cosmosmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/chaincfg" + "github.com/zeta-chain/node/testutil" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/pkg/memo" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" + "github.com/zeta-chain/node/zetaclient/config" + "github.com/zeta-chain/node/zetaclient/keys" + "github.com/zeta-chain/node/zetaclient/testutils" + "github.com/zeta-chain/node/zetaclient/testutils/mocks" +) + +// createTestBtcEvent creates a test BTC inbound event +func createTestBtcEvent( + t *testing.T, + net *chaincfg.Params, + memo []byte, + memoStd *memo.InboundMemo, +) observer.BTCInboundEvent { + return observer.BTCInboundEvent{ + FromAddress: sample.BtcAddressP2WPKH(t, net), + ToAddress: sample.EthAddress().Hex(), + MemoBytes: memo, + MemoStd: memoStd, + TxHash: sample.Hash().Hex(), + BlockNumber: 123456, + } +} + +func Test_CheckProcessability(t *testing.T) { + // setup compliance config + cfg := config.Config{ + ComplianceConfig: sample.ComplianceConfig(), + } + config.LoadComplianceConfig(cfg) + + // test cases + tests := []struct { + name string + event *observer.BTCInboundEvent + expected observer.InboundProcessability + }{ + { + name: "should return InboundProcessabilityGood for a processable inbound event", + event: &observer.BTCInboundEvent{ + FromAddress: "tb1quhassyrlj43qar0mn0k5sufyp6mazmh2q85lr6ex8ehqfhxpzsksllwrsu", + ToAddress: testutils.TSSAddressBTCAthens3, + }, + expected: observer.InboundProcessabilityGood, + }, + { + name: "should return InboundProcessabilityComplianceViolation for a restricted sender address", + event: &observer.BTCInboundEvent{ + FromAddress: sample.RestrictedBtcAddressTest, + ToAddress: testutils.TSSAddressBTCAthens3, + }, + expected: observer.InboundProcessabilityComplianceViolation, + }, + { + name: "should return InboundProcessabilityComplianceViolation for a restricted receiver address in standard memo", + event: &observer.BTCInboundEvent{ + FromAddress: "tb1quhassyrlj43qar0mn0k5sufyp6mazmh2q85lr6ex8ehqfhxpzsksllwrsu", + ToAddress: testutils.TSSAddressBTCAthens3, + MemoStd: &memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + Receiver: common.HexToAddress(sample.RestrictedEVMAddressTest), + }, + }, + }, + expected: observer.InboundProcessabilityComplianceViolation, + }, + { + name: "should return InboundProcessabilityComplianceViolation for a restricted revert address in standard memo", + event: &observer.BTCInboundEvent{ + FromAddress: "tb1quhassyrlj43qar0mn0k5sufyp6mazmh2q85lr6ex8ehqfhxpzsksllwrsu", + ToAddress: testutils.TSSAddressBTCAthens3, + MemoStd: &memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + RevertOptions: crosschaintypes.RevertOptions{ + RevertAddress: sample.RestrictedBtcAddressTest, + }, + }, + }, + }, + expected: observer.InboundProcessabilityComplianceViolation, + }, + { + name: "should return InboundProcessabilityDonation for a donation inbound event", + event: &observer.BTCInboundEvent{ + FromAddress: "tb1quhassyrlj43qar0mn0k5sufyp6mazmh2q85lr6ex8ehqfhxpzsksllwrsu", + ToAddress: testutils.TSSAddressBTCAthens3, + MemoBytes: []byte(constant.DonationMessage), + }, + expected: observer.InboundProcessabilityDonation, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.event.Processability() + require.Equal(t, tt.expected, result) + }) + } +} + +func Test_DecodeEventMemoBytes(t *testing.T) { + // test cases + tests := []struct { + name string + chainID int64 + event *observer.BTCInboundEvent + expectedMemoStd *memo.InboundMemo + expectedReceiver common.Address + donation bool + errMsg string + }{ + { + name: "should decode standard memo bytes successfully", + chainID: chains.BitcoinTestnet.ChainId, + event: &observer.BTCInboundEvent{ + // a deposit and call + MemoBytes: testutil.HexToBytes( + t, + "5a0110032d07a9cbd57dcca3e2cf966c88bc874445b6e3b60d68656c6c6f207361746f736869", + ), + }, + expectedMemoStd: &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDepositAndCall, + DataFlags: 3, // reciever + payload + }, + FieldsV0: memo.FieldsV0{ + Receiver: common.HexToAddress("0x2D07A9CBd57DCca3E2cF966C88Bc874445b6E3B6"), + Payload: []byte("hello satoshi"), + }, + }, + }, + { + name: "should fall back to legacy memo successfully", + chainID: chains.BitcoinTestnet.ChainId, + event: &observer.BTCInboundEvent{ + // raw address + payload + MemoBytes: testutil.HexToBytes(t, "2d07a9cbd57dcca3e2cf966c88bc874445b6e3b668656c6c6f207361746f736869"), + }, + expectedReceiver: common.HexToAddress("0x2D07A9CBd57DCca3E2cF966C88Bc874445b6E3B6"), + }, + { + name: "should disable standard memo for Bitcoin mainnet", + chainID: chains.BitcoinMainnet.ChainId, + event: &observer.BTCInboundEvent{ + // a deposit and call + MemoBytes: testutil.HexToBytes( + t, + "5a0110032d07a9cbd57dcca3e2cf966c88bc874445b6e3b60d68656c6c6f207361746f736869", + ), + }, + expectedReceiver: common.HexToAddress("0x5A0110032d07A9cbd57dcCa3e2Cf966c88bC8744"), + }, + { + name: "should do nothing for donation message", + chainID: chains.BitcoinTestnet.ChainId, + event: &observer.BTCInboundEvent{ + MemoBytes: []byte(constant.DonationMessage), + }, + donation: true, + }, + { + name: "should return error if standard memo contains improper data", + chainID: chains.BitcoinTestnet.ChainId, + event: &observer.BTCInboundEvent{ + // a deposit and call, receiver is empty ZEVM address + MemoBytes: testutil.HexToBytes( + t, + "5a01100300000000000000000000000000000000000000000d68656c6c6f207361746f736869", + ), + }, + errMsg: "standard memo contains improper data", + }, + { + name: "should return error if standard memo validation failed", + chainID: chains.BitcoinTestnet.ChainId, + event: &observer.BTCInboundEvent{ + // a no asset call opCode passed, not supported at the moment + MemoBytes: testutil.HexToBytes( + t, + "5a0120032d07a9cbd57dcca3e2cf966c88bc874445b6e3b60d68656c6c6f207361746f736869", + ), + }, + errMsg: "invalid standard memo for bitcoin", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.event.DecodeMemoBytes(tt.chainID) + if tt.errMsg != "" { + require.Contains(t, err.Error(), tt.errMsg) + return + } + require.NoError(t, err) + + // donation message will skip decoding, so ToAddress will be left empty + if tt.donation { + require.Empty(t, tt.event.ToAddress) + return + } + + // if it's a standard memo + if tt.expectedMemoStd != nil { + require.NotNil(t, tt.event.MemoStd) + require.Equal(t, tt.expectedMemoStd.Receiver.Hex(), tt.event.ToAddress) + require.Equal(t, tt.expectedMemoStd, tt.event.MemoStd) + } else { + // if it's a legacy memo, check receiver address only + require.Equal(t, tt.expectedReceiver.Hex(), tt.event.ToAddress) + } + }) + } +} + +func Test_ValidateStandardMemo(t *testing.T) { + // test cases + tests := []struct { + name string + memo memo.InboundMemo + errMsg string + }{ + { + name: "validation should pass for a valid standard memo", + memo: memo.InboundMemo{ + Header: memo.Header{ + OpCode: memo.OpCodeDepositAndCall, + }, + FieldsV0: memo.FieldsV0{ + RevertOptions: crosschaintypes.RevertOptions{ + RevertAddress: sample.BtcAddressP2WPKH(t, &chaincfg.TestNet3Params), + }, + }, + }, + }, + { + name: "NoAssetCall is disabled for Bitcoin", + memo: memo.InboundMemo{ + Header: memo.Header{ + OpCode: memo.OpCodeCall, + }, + }, + errMsg: "NoAssetCall is disabled for Bitcoin", + }, + { + name: "should return error on invalid revert address", + memo: memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + RevertOptions: crosschaintypes.RevertOptions{ + // not a BTC address + RevertAddress: "0x2D07A9CBd57DCca3E2cF966C88Bc874445b6E3B6", + }, + }, + }, + errMsg: "invalid revert address in memo", + }, + { + name: "should return error if revert address is not a supported address type", + memo: memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + RevertOptions: crosschaintypes.RevertOptions{ + // address not supported + RevertAddress: "035e4ae279bd416b5da724972c9061ec6298dac020d1e3ca3f06eae715135cdbec", + }, + }, + }, + errMsg: "unsupported revert address in memo", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := observer.ValidateStandardMemo(tt.memo, chains.BitcoinTestnet.ChainId) + if tt.errMsg != "" { + require.Contains(t, err.Error(), tt.errMsg) + return + } + require.NoError(t, err) + }) + } +} + +func Test_CheckEventProcessability(t *testing.T) { + // can use any bitcoin chain for testing + chain := chains.BitcoinMainnet + params := mocks.MockChainParams(chain.ChainId, 10) + + // create test observer + ob := MockBTCObserver(t, chain, params, nil) + + // setup compliance config + cfg := config.Config{ + ComplianceConfig: sample.ComplianceConfig(), + } + config.LoadComplianceConfig(cfg) + + // test cases + tests := []struct { + name string + event observer.BTCInboundEvent + result bool + }{ + { + name: "should return true for processable event", + event: createTestBtcEvent(t, &chaincfg.MainNetParams, []byte("a memo"), nil), + result: true, + }, + { + name: "should return false on donation message", + event: createTestBtcEvent(t, &chaincfg.MainNetParams, []byte(constant.DonationMessage), nil), + result: false, + }, + { + name: "should return false on compliance violation", + event: createTestBtcEvent(t, &chaincfg.MainNetParams, []byte("a memo"), &memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + Receiver: common.HexToAddress(sample.RestrictedEVMAddressTest), + }, + }), + result: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ob.CheckEventProcessability(tt.event) + require.Equal(t, tt.result, result) + }) + } +} + +func Test_NewInboundVoteFromLegacyMemo(t *testing.T) { + // can use any bitcoin chain for testing + chain := chains.BitcoinMainnet + params := mocks.MockChainParams(chain.ChainId, 10) + + // create test observer + ob := MockBTCObserver(t, chain, params, nil) + zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() + ob.WithZetacoreClient(zetacoreClient) + + t.Run("should create new inbound vote msg V1", func(t *testing.T) { + // create test event + event := createTestBtcEvent(t, &chaincfg.MainNetParams, []byte("dummy memo"), nil) + + // test amount + amountSats := big.NewInt(1000) + + // expected vote + expectedVote := crosschaintypes.MsgVoteInbound{ + Sender: event.FromAddress, + SenderChainId: chain.ChainId, + TxOrigin: event.FromAddress, + Receiver: event.ToAddress, + ReceiverChain: ob.ZetacoreClient().Chain().ChainId, + Amount: cosmosmath.NewUint(amountSats.Uint64()), + Message: hex.EncodeToString(event.MemoBytes), + InboundHash: event.TxHash, + InboundBlockHeight: event.BlockNumber, + CallOptions: &crosschaintypes.CallOptions{ + GasLimit: 0, + }, + CoinType: coin.CoinType_Gas, + ProtocolContractVersion: crosschaintypes.ProtocolContractVersion_V1, + RevertOptions: crosschaintypes.NewEmptyRevertOptions(), // ignored by V1 + } + + // create new inbound vote V1 + vote := ob.NewInboundVoteFromLegacyMemo(&event, amountSats) + require.Equal(t, expectedVote, *vote) + }) +} + +func Test_NewInboundVoteFromStdMemo(t *testing.T) { + // can use any bitcoin chain for testing + chain := chains.BitcoinMainnet + params := mocks.MockChainParams(chain.ChainId, 10) + + // create test observer + ob := MockBTCObserver(t, chain, params, nil) + zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() + ob.WithZetacoreClient(zetacoreClient) + + t.Run("should create new inbound vote msg with standard memo", func(t *testing.T) { + // create revert options + revertOptions := crosschaintypes.NewEmptyRevertOptions() + revertOptions.RevertAddress = sample.BtcAddressP2WPKH(t, &chaincfg.MainNetParams) + + // create test event + receiver := sample.EthAddress() + event := createTestBtcEvent(t, &chaincfg.MainNetParams, []byte("dymmy"), &memo.InboundMemo{ + FieldsV0: memo.FieldsV0{ + Receiver: receiver, + Payload: []byte("some payload"), + RevertOptions: revertOptions, + }, + }) + + // test amount + amountSats := big.NewInt(1000) + + // expected vote + memoBytesExpected := append(event.MemoStd.Receiver.Bytes(), event.MemoStd.Payload...) + expectedVote := crosschaintypes.MsgVoteInbound{ + Sender: revertOptions.RevertAddress, // should be overridden by revert address + SenderChainId: chain.ChainId, + TxOrigin: event.FromAddress, + Receiver: event.ToAddress, + ReceiverChain: ob.ZetacoreClient().Chain().ChainId, + Amount: cosmosmath.NewUint(amountSats.Uint64()), + Message: hex.EncodeToString(memoBytesExpected), // a simulated legacy memo + InboundHash: event.TxHash, + InboundBlockHeight: event.BlockNumber, + CallOptions: &crosschaintypes.CallOptions{ + GasLimit: 0, + }, + CoinType: coin.CoinType_Gas, + ProtocolContractVersion: crosschaintypes.ProtocolContractVersion_V1, + RevertOptions: crosschaintypes.NewEmptyRevertOptions(), // ignored by V1 + } + + // create new inbound vote V1 with standard memo + vote := ob.NewInboundVoteFromStdMemo(&event, amountSats) + require.Equal(t, expectedVote, *vote) + }) +} diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 1461096763..b08fbea18c 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -6,50 +6,22 @@ import ( "fmt" "math/big" - cosmosmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/zeta-chain/node/pkg/coin" - "github.com/zeta-chain/node/pkg/memo" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/interfaces" - "github.com/zeta-chain/node/zetaclient/compliance" - "github.com/zeta-chain/node/zetaclient/config" zctx "github.com/zeta-chain/node/zetaclient/context" + "github.com/zeta-chain/node/zetaclient/logs" "github.com/zeta-chain/node/zetaclient/types" "github.com/zeta-chain/node/zetaclient/zetacore" ) -// BTCInboundEvent represents an incoming transaction event -type BTCInboundEvent struct { - // FromAddress is the first input address - FromAddress string - - // ToAddress is the TSS address - ToAddress string - - // Value is the amount of BTC - Value float64 - - // DepositorFee is the deposit fee - DepositorFee float64 - - // MemoBytes is the memo of inbound - MemoBytes []byte - - // BlockNumber is the block number of the inbound - BlockNumber uint64 - - // TxHash is the hash of the inbound - TxHash string -} - // WatchInbound watches Bitcoin chain for inbounds on a ticker // It starts a ticker and run ObserveInbound // TODO(revamp): move all ticker related methods in the same file @@ -100,8 +72,6 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { // ObserveInbound observes the Bitcoin chain for inbounds and post votes to zetacore // TODO(revamp): simplify this function into smaller functions func (ob *Observer) ObserveInbound(ctx context.Context) error { - zetaCoreClient := ob.ZetacoreClient() - // get and update latest block height currentBlock, err := ob.btcClient.GetBlockCount() if err != nil { @@ -166,22 +136,11 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { // post inbound vote message to zetacore for _, event := range events { - msg := ob.GetInboundVoteMessageFromBtcEvent(event) + msg := ob.GetInboundVoteFromBtcEvent(event) if msg != nil { - zetaHash, ballot, err := zetaCoreClient.PostVoteInbound( - ctx, - zetacore.PostVoteInboundGasLimit, - zetacore.PostVoteInboundExecutionGasLimit, - msg, - ) + _, err = ob.PostVoteInbound(ctx, msg, zetacore.PostVoteInboundExecutionGasLimit) if err != nil { - ob.logger.Inbound.Error(). - Err(err). - Msgf("observeInboundBTC: error posting to zetacore for tx %s", event.TxHash) - return err // we have to re-scan this block next time - } else if zetaHash != "" { - ob.logger.Inbound.Info().Msgf("observeInboundBTC: PostVoteInbound zeta tx hash: %s inbound %s ballot %s fee %v", - zetaHash, event.TxHash, ballot, event.DepositorFee) + return errors.Wrapf(err, "error PostVoteInbound") // we have to re-scan this block next time } } } @@ -319,7 +278,7 @@ func (ob *Observer) CheckReceiptForBtcTxHash(ctx context.Context, txHash string, return "", errors.New("no btc deposit event found") } - msg := ob.GetInboundVoteMessageFromBtcEvent(event) + msg := ob.GetInboundVoteFromBtcEvent(event) if msg == nil { return "", errors.New("no message built for btc sent to TSS") } @@ -383,52 +342,43 @@ func FilterAndParseIncomingTx( return events, nil } -// GetInboundVoteMessageFromBtcEvent converts a BTCInboundEvent to a MsgVoteInbound to enable voting on the inbound on zetacore -func (ob *Observer) GetInboundVoteMessageFromBtcEvent(inbound *BTCInboundEvent) *crosschaintypes.MsgVoteInbound { - ob.logger.Inbound.Debug().Msgf("Processing inbound: %s", inbound.TxHash) - amount := big.NewFloat(inbound.Value) - amount = amount.Mul(amount, big.NewFloat(1e8)) - amountInt, _ := amount.Int(nil) - message := hex.EncodeToString(inbound.MemoBytes) - - // compliance check - // if the inbound contains restricted addresses, return nil - if ob.DoesInboundContainsRestrictedAddress(inbound) { +// GetInboundVoteFromBtcEvent converts a BTCInboundEvent to a MsgVoteInbound to enable voting on the inbound on zetacore +func (ob *Observer) GetInboundVoteFromBtcEvent(event *BTCInboundEvent) *crosschaintypes.MsgVoteInbound { + // prepare logger fields + lf := map[string]any{ + logs.FieldModule: logs.ModNameInbound, + logs.FieldMethod: "GetInboundVoteFromBtcEvent", + logs.FieldChain: ob.Chain().ChainId, + logs.FieldTx: event.TxHash, + } + + // decode event memo bytes + err := event.DecodeMemoBytes(ob.Chain().ChainId) + if err != nil { + ob.Logger().Inbound.Info().Fields(lf).Msgf("invalid memo bytes: %s", hex.EncodeToString(event.MemoBytes)) return nil } - return zetacore.GetInboundVoteMessage( - inbound.FromAddress, - ob.Chain().ChainId, - inbound.FromAddress, - inbound.FromAddress, - ob.ZetacoreClient().Chain().ChainId, - cosmosmath.NewUintFromBigInt(amountInt), - message, - inbound.TxHash, - inbound.BlockNumber, - 0, - coin.CoinType_Gas, - "", - ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), - 0, - ) -} + // check if the event is processable + if !ob.CheckEventProcessability(*event) { + return nil + } -// DoesInboundContainsRestrictedAddress returns true if the inbound contains restricted addresses -// TODO(revamp): move all compliance related functions in a specific file -func (ob *Observer) DoesInboundContainsRestrictedAddress(inTx *BTCInboundEvent) bool { - receiver := "" - parsedAddress, _, err := memo.DecodeLegacyMemoHex(hex.EncodeToString(inTx.MemoBytes)) - if err == nil && parsedAddress != (ethcommon.Address{}) { - receiver = parsedAddress.Hex() + // convert the amount to integer (satoshis) + amountSats, err := bitcoin.GetSatoshis(event.Value) + if err != nil { + ob.Logger().Inbound.Error().Err(err).Fields(lf).Msgf("can't convert value %f to satoshis", event.Value) + return nil } - if config.ContainRestrictedAddress(inTx.FromAddress, receiver) { - compliance.PrintComplianceLog(ob.logger.Inbound, ob.logger.Compliance, - false, ob.Chain().ChainId, inTx.TxHash, inTx.FromAddress, receiver, "BTC") - return true + amountInt := big.NewInt(amountSats) + + // create inbound vote message contract V1 for legacy memo + if event.MemoStd == nil { + return ob.NewInboundVoteFromLegacyMemo(event, amountInt) } - return false + + // create inbound vote message for standard memo + return ob.NewInboundVoteFromStdMemo(event, amountInt) } // GetBtcEvent returns a valid BTCInboundEvent or nil @@ -489,7 +439,7 @@ func GetBtcEventWithoutWitness( // 2nd vout must be a valid OP_RETURN memo vout1 := tx.Vout[1] - memo, found, err = bitcoin.DecodeOpReturnMemo(vout1.ScriptPubKey.Hex, tx.Txid) + memo, found, err = bitcoin.DecodeOpReturnMemo(vout1.ScriptPubKey.Hex) if err != nil { logger.Error().Err(err).Msgf("GetBtcEvent: error decoding OP_RETURN memo: %s", vout1.ScriptPubKey.Hex) return nil, nil diff --git a/zetaclient/chains/bitcoin/observer/inbound_test.go b/zetaclient/chains/bitcoin/observer/inbound_test.go index 8b01e222a1..838315b7b8 100644 --- a/zetaclient/chains/bitcoin/observer/inbound_test.go +++ b/zetaclient/chains/bitcoin/observer/inbound_test.go @@ -18,9 +18,13 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/testutil" + "github.com/zeta-chain/node/testutil/sample" "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" clientcommon "github.com/zeta-chain/node/zetaclient/common" + "github.com/zeta-chain/node/zetaclient/keys" "github.com/zeta-chain/node/zetaclient/testutils" "github.com/zeta-chain/node/zetaclient/testutils/mocks" "github.com/zeta-chain/node/zetaclient/testutils/testrpc" @@ -138,6 +142,77 @@ func TestAvgFeeRateBlock828440Errors(t *testing.T) { }) } +func Test_GetInboundVoteFromBtcEvent(t *testing.T) { + // can use any bitcoin chain for testing + chain := chains.BitcoinMainnet + params := mocks.MockChainParams(chain.ChainId, 10) + + // create test observer + ob := MockBTCObserver(t, chain, params, nil) + zetacoreClient := mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}).WithZetaChain() + ob.WithZetacoreClient(zetacoreClient) + + // test cases + tests := []struct { + name string + event *observer.BTCInboundEvent + nilVote bool + }{ + { + name: "should return vote for standard memo", + event: &observer.BTCInboundEvent{ + FromAddress: sample.BtcAddressP2WPKH(t, &chaincfg.MainNetParams), + // a deposit and call + MemoBytes: testutil.HexToBytes( + t, + "5a0110032d07a9cbd57dcca3e2cf966c88bc874445b6e3b60d68656c6c6f207361746f736869", + ), + }, + }, + { + name: "should return vote for legacy memo", + event: &observer.BTCInboundEvent{ + // raw address + payload + MemoBytes: testutil.HexToBytes(t, "2d07a9cbd57dcca3e2cf966c88bc874445b6e3b668656c6c6f207361746f736869"), + }, + }, + { + name: "should return nil if unable to decode memo", + event: &observer.BTCInboundEvent{ + // standard memo that carries payload only, receiver address is empty + MemoBytes: testutil.HexToBytes(t, "5a0110020d68656c6c6f207361746f736869"), + }, + nilVote: true, + }, + { + name: "should return nil on donation message", + event: &observer.BTCInboundEvent{ + MemoBytes: []byte(constant.DonationMessage), + }, + nilVote: true, + }, + { + name: "should return nil on invalid deposit value", + event: &observer.BTCInboundEvent{ + Value: -1, // invalid value + MemoBytes: testutil.HexToBytes(t, "2d07a9cbd57dcca3e2cf966c88bc874445b6e3b668656c6c6f207361746f736869"), + }, + nilVote: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := ob.GetInboundVoteFromBtcEvent(tt.event) + if tt.nilVote { + require.Nil(t, msg) + } else { + require.NotNil(t, msg) + } + }) + } +} + func TestGetSenderAddressByVin(t *testing.T) { // https://mempool.space/tx/3618e869f9e87863c0f1cc46dbbaa8b767b4a5d6d60b143c2c50af52b257e867 txHash := "3618e869f9e87863c0f1cc46dbbaa8b767b4a5d6d60b143c2c50af52b257e867" diff --git a/zetaclient/chains/bitcoin/observer/witness.go b/zetaclient/chains/bitcoin/observer/witness.go index 86b22f95cf..22ce75719b 100644 --- a/zetaclient/chains/bitcoin/observer/witness.go +++ b/zetaclient/chains/bitcoin/observer/witness.go @@ -130,7 +130,7 @@ func tryExtractOpRet(tx btcjson.TxRawResult, logger zerolog.Logger) []byte { return nil } - memo, found, err := bitcoin.DecodeOpReturnMemo(tx.Vout[1].ScriptPubKey.Hex, tx.Txid) + memo, found, err := bitcoin.DecodeOpReturnMemo(tx.Vout[1].ScriptPubKey.Hex) if err != nil { logger.Error().Err(err).Msgf("tryExtractOpRet: error decoding OP_RETURN memo: %s", tx.Vout[1].ScriptPubKey.Hex) return nil diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 9614010a65..6f394ef81d 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -2,10 +2,8 @@ package bitcoin // #nosec G507 ripemd160 required for bitcoin address encoding import ( - "bytes" "encoding/hex" "fmt" - "strconv" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil" @@ -16,7 +14,6 @@ import ( "golang.org/x/crypto/ripemd160" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/pkg/constant" ) const ( @@ -158,27 +155,49 @@ func DecodeScriptP2PKH(scriptHex string, net *chaincfg.Params) (string, error) { // DecodeOpReturnMemo decodes memo from OP_RETURN script // returns (memo, found, error) -func DecodeOpReturnMemo(scriptHex string, txid string) ([]byte, bool, error) { - if len(scriptHex) >= 4 && scriptHex[:2] == "6a" { // OP_RETURN - memoSize, err := strconv.ParseInt(scriptHex[2:4], 16, 32) - if err != nil { - return nil, false, errors.Wrapf(err, "error decoding memo size: %s", scriptHex) +func DecodeOpReturnMemo(scriptHex string) ([]byte, bool, error) { + // decode hex script + scriptBytes, err := hex.DecodeString(scriptHex) + if err != nil { + return nil, false, errors.Wrapf(err, "error decoding script hex: %s", scriptHex) + } + + // skip non-OP_RETURN script + // OP_RETURN script has to be at least 2 bytes: [OP_RETURN + dataLen] + if len(scriptBytes) < 2 || scriptBytes[0] != txscript.OP_RETURN { + return nil, false, nil + } + + // extract appended data in the OP_RETURN script + var memoBytes []byte + var memoSize = scriptBytes[1] + switch { + case memoSize < txscript.OP_PUSHDATA1: + // memo size has to match the actual data + if int(memoSize) != (len(scriptBytes) - 2) { + return nil, false, fmt.Errorf("memo size mismatch: %d != %d", memoSize, (len(scriptBytes) - 2)) } - if int(memoSize) != (len(scriptHex)-4)/2 { - return nil, false, fmt.Errorf("memo size mismatch: %d != %d", memoSize, (len(scriptHex)-4)/2) + memoBytes = scriptBytes[2:] + case memoSize == txscript.OP_PUSHDATA1: + // when data size >= OP_PUSHDATA1 (76), Bitcoin uses 2 bytes to represent the length: [OP_PUSHDATA1 + dataLen] + // see: https://github.com/btcsuite/btcd/blob/master/txscript/scriptbuilder.go#L183 + if len(scriptBytes) < 3 { + return nil, false, fmt.Errorf("script too short: %s", scriptHex) } + memoSize = scriptBytes[2] - memoBytes, err := hex.DecodeString(scriptHex[4:]) - if err != nil { - return nil, false, errors.Wrapf(err, "error hex decoding memo: %s", scriptHex) - } - if bytes.Equal(memoBytes, []byte(constant.DonationMessage)) { - return nil, false, fmt.Errorf("donation tx: %s", txid) + // memo size has to match the actual data + if int(memoSize) != (len(scriptBytes) - 3) { + return nil, false, fmt.Errorf("memo size mismatch: %d != %d", memoSize, (len(scriptBytes) - 3)) } - return memoBytes, true, nil + memoBytes = scriptBytes[3:] + default: + // should never happen + // OP_RETURN script won't carry more than 80 bytes + return nil, false, fmt.Errorf("invalid OP_RETURN script: %s", scriptHex) } - return nil, false, nil + return memoBytes, true, nil } // DecodeScript decodes memo wrapped in an inscription like script in witness diff --git a/zetaclient/chains/bitcoin/tx_script_test.go b/zetaclient/chains/bitcoin/tx_script_test.go index cf54b4553f..394a5d8608 100644 --- a/zetaclient/chains/bitcoin/tx_script_test.go +++ b/zetaclient/chains/bitcoin/tx_script_test.go @@ -1,6 +1,7 @@ package bitcoin_test import ( + "bytes" "encoding/hex" "path" "strings" @@ -11,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/testutil" "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/testutils" ) @@ -331,80 +332,95 @@ func TestDecodeVoutP2PKHErrors(t *testing.T) { } func TestDecodeOpReturnMemo(t *testing.T) { - // load archived inbound raw result - // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa - chain := chains.BitcoinMainnet - txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" - scriptHex := "6a1467ed0bcc4e1256bc2ce87d22e190d63a120114bf" - rawResult := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) - require.True(t, len(rawResult.Vout) >= 2) - require.Equal(t, scriptHex, rawResult.Vout[1].ScriptPubKey.Hex) - - t.Run("should decode memo from OP_RETURN output", func(t *testing.T) { - memo, found, err := bitcoin.DecodeOpReturnMemo(rawResult.Vout[1].ScriptPubKey.Hex, txHash) - require.NoError(t, err) - require.True(t, found) - // [OP_RETURN, 0x14,<20-byte-hash>] - require.Equal(t, scriptHex[4:], hex.EncodeToString(memo)) - }) - t.Run("should return nil memo non-OP_RETURN output", func(t *testing.T) { - // modify the OP_RETURN to OP_1 - scriptInvalid := strings.Replace(scriptHex, "6a", "51", 1) - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptInvalid, txHash) - require.NoError(t, err) - require.False(t, found) - require.Nil(t, memo) - }) - t.Run("should return nil memo on invalid script", func(t *testing.T) { - // use known short script - scriptInvalid := "00" - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptInvalid, txHash) - require.NoError(t, err) - require.False(t, found) - require.Nil(t, memo) - }) + tests := []struct { + name string + scriptHex string + found bool + expected []byte + }{ + { + name: "should decode memo from OP_RETURN data, size < 76(OP_PUSHDATA1)", + scriptHex: "6a1467ed0bcc4e1256bc2ce87d22e190d63a120114bf", + found: true, + expected: testutil.HexToBytes(t, "67ed0bcc4e1256bc2ce87d22e190d63a120114bf"), + }, + { + name: "should decode memo from OP_RETURN data, size >= 76(OP_PUSHDATA1)", + scriptHex: "6a4c4f" + // 79 bytes memo + "5a0110070a30d55c1031d30dab3b3d85f47b8f1d03df2d480961207061796c6f61642c626372743171793970716d6b32706439737636336732376a7438723635377779306439756565347832647432", + found: true, + expected: testutil.HexToBytes( + t, + "5a0110070a30d55c1031d30dab3b3d85f47b8f1d03df2d480961207061796c6f61642c626372743171793970716d6b32706439737636336732376a7438723635377779306439756565347832647432", + ), + }, + { + name: "should return nil memo for non-OP_RETURN script", + scriptHex: "511467ed0bcc4e1256bc2ce87d22e190d63a120114bf", // 0x51, OP_1 + found: false, + expected: nil, + }, + { + name: "should return nil memo for script less than 2 bytes", + scriptHex: "00", // 1 byte only + found: false, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + memo, found, err := bitcoin.DecodeOpReturnMemo(tt.scriptHex) + require.NoError(t, err) + require.Equal(t, tt.found, found) + require.True(t, bytes.Equal(tt.expected, memo)) + }) + } } func TestDecodeOpReturnMemoErrors(t *testing.T) { - // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa - txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" - scriptHex := "6a1467ed0bcc4e1256bc2ce87d22e190d63a120114bf" - - t.Run("should return error on invalid memo size", func(t *testing.T) { - // use invalid memo size - scriptInvalid := strings.Replace(scriptHex, "6a14", "6axy", 1) - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptInvalid, txHash) - require.ErrorContains(t, err, "error decoding memo size") - require.False(t, found) - require.Nil(t, memo) - }) - - t.Run("should return error on memo size mismatch", func(t *testing.T) { - // use wrong memo size - scriptInvalid := strings.Replace(scriptHex, "6a14", "6a13", 1) - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptInvalid, txHash) - require.ErrorContains(t, err, "memo size mismatch") - require.False(t, found) - require.Nil(t, memo) - }) - - t.Run("should return error on invalid hex", func(t *testing.T) { - // use invalid hex - scriptInvalid := strings.Replace(scriptHex, "6a1467", "6a14xy", 1) - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptInvalid, txHash) - require.ErrorContains(t, err, "error hex decoding memo") - require.False(t, found) - require.Nil(t, memo) - }) + tests := []struct { + name string + scriptHex string + errMsg string + }{ + { + name: "should return error on invalid hex", + scriptHex: "6a14xy", + errMsg: "error decoding script hex", + }, + { + name: "should return error on memo size < 76 (OP_PUSHDATA1) mismatch", + scriptHex: "6a15" + // 20 bytes memo, but length is set to 21(0x15) + "67ed0bcc4e1256bc2ce87d22e190d63a120114bf", + errMsg: "memo size mismatch", + }, + { + name: "should return error when memo size >= 76 (OP_PUSHDATA1) but script is too short", + scriptHex: "6a4c", // 2 bytes only, requires at least 3 bytes + errMsg: "script too short", + }, + { + name: "should return error on memo size >= 76 (OP_PUSHDATA1) mismatch", + scriptHex: "6a4c4e" + // 79 bytes memo, but length is set to 78(0x4e) + "5a0110070a30d55c1031d30dab3b3d85f47b8f1d03df2d480961207061796c6f61642c626372743171793970716d6b32706439737636336732376a7438723635377779306439756565347832647432", + errMsg: "memo size mismatch", + }, + { + name: "should return error on invalid OP_RETURN", + scriptHex: "6a4d0001", // OP_PUSHDATA2, length is set to 256 (0x0001, little-endian) + errMsg: "invalid OP_RETURN script", + }, + } - t.Run("should return nil memo on donation tx", func(t *testing.T) { - // use donation sctipt "6a0a4920616d207269636821" - scriptDonation := "6a0a" + hex.EncodeToString([]byte(constant.DonationMessage)) - memo, found, err := bitcoin.DecodeOpReturnMemo(scriptDonation, txHash) - require.ErrorContains(t, err, "donation tx") - require.False(t, found) - require.Nil(t, memo) - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + memo, found, err := bitcoin.DecodeOpReturnMemo(tt.scriptHex) + require.ErrorContains(t, err, tt.errMsg) + require.False(t, found) + require.Nil(t, memo) + }) + } } func TestDecodeSenderFromScript(t *testing.T) { diff --git a/zetaclient/chains/evm/observer/v2_inbound.go b/zetaclient/chains/evm/observer/v2_inbound.go index b19f0e9f85..9688851af6 100644 --- a/zetaclient/chains/evm/observer/v2_inbound.go +++ b/zetaclient/chains/evm/observer/v2_inbound.go @@ -192,7 +192,7 @@ func (ob *Observer) newDepositInboundVote(event *gatewayevm.GatewayEVMDeposited) hex.EncodeToString(event.Payload), event.Raw.TxHash.Hex(), event.Raw.BlockNumber, - 1_500_000, + zetacore.PostVoteInboundCallOptionsGasLimit, coinType, event.Asset.Hex(), event.Raw.Index, @@ -328,7 +328,7 @@ func (ob *Observer) newCallInboundVote(event *gatewayevm.GatewayEVMCalled) types hex.EncodeToString(event.Payload), event.Raw.TxHash.Hex(), event.Raw.BlockNumber, - 1_500_000, + zetacore.PostVoteInboundCallOptionsGasLimit, coin.CoinType_NoAssetCall, "", event.Raw.Index, diff --git a/zetaclient/zetacore/constant.go b/zetaclient/zetacore/constant.go index 1457dd0c58..ab13e741d0 100644 --- a/zetaclient/zetacore/constant.go +++ b/zetaclient/zetacore/constant.go @@ -24,6 +24,9 @@ const ( // PostVoteInboundMessagePassingExecutionGasLimit is the gas limit for voting on, and executing ,observed inbound tx related to message passing (coin_type == zeta) PostVoteInboundMessagePassingExecutionGasLimit = 4_000_000 + // PostVoteInboundCallOptionsGasLimit is the gas limit for inbound call options + PostVoteInboundCallOptionsGasLimit uint64 = 1_500_000 + // AddOutboundTrackerGasLimit is the gas limit for adding tx hash to out tx tracker AddOutboundTrackerGasLimit = 200_000 From 3eefe9c7215ad0dd76c18117a87922e73237dbb3 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Fri, 25 Oct 2024 21:03:04 +0200 Subject: [PATCH 03/34] test(e2e): update testdapp contract in deploy test (#3042) --- e2e/e2etests/test_deploy_contract.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/e2e/e2etests/test_deploy_contract.go b/e2e/e2etests/test_deploy_contract.go index bddef5b95d..e2623a1381 100644 --- a/e2e/e2etests/test_deploy_contract.go +++ b/e2e/e2etests/test_deploy_contract.go @@ -6,9 +6,9 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/zeta-chain/node/e2e/contracts/testdapp" "github.com/zeta-chain/node/e2e/runner" "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/contracts/testdappv2" ) // deployFunc is a function that deploys a contract @@ -42,11 +42,9 @@ func TestDeployContract(r *runner.E2ERunner, args []string) { // deployZEVMTestDApp deploys the TestDApp contract on ZetaChain func deployZEVMTestDApp(r *runner.E2ERunner) (ethcommon.Address, error) { - addr, tx, _, err := testdapp.DeployTestDApp( + addr, tx, _, err := testdappv2.DeployTestDAppV2( r.ZEVMAuth, r.ZEVMClient, - r.ConnectorZEVMAddr, - r.WZetaAddr, ) if err != nil { return addr, err @@ -63,11 +61,9 @@ func deployZEVMTestDApp(r *runner.E2ERunner) (ethcommon.Address, error) { // deployEVMTestDApp deploys the TestDApp contract on Ethereum func deployEVMTestDApp(r *runner.E2ERunner) (ethcommon.Address, error) { - addr, tx, _, err := testdapp.DeployTestDApp( + addr, tx, _, err := testdappv2.DeployTestDAppV2( r.EVMAuth, r.EVMClient, - r.ConnectorEthAddr, - r.ZetaEthAddr, ) if err != nil { return addr, err From 4c8c9262c5305e01070c77d5d687c28256a0d669 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 28 Oct 2024 09:09:42 +0000 Subject: [PATCH 04/34] fix: wrong block hash in subscribe new heads (#3047) * fix wrong hash in subscribe new heads * fix other fields and block unit tests * changelog and fmt * fix unit tests missing mocks * remove unused field from header * PR comment --- changelog.md | 1 + rpc/backend/blocks.go | 23 +++++++-- rpc/backend/blocks_test.go | 36 +++++++++++++- rpc/backend/call_tx_test.go | 11 +++++ rpc/backend/sign_tx_test.go | 4 ++ rpc/backend/utils.go | 14 ++++++ rpc/namespaces/ethereum/eth/filters/api.go | 56 +++++++++++++++++----- rpc/types/block.go | 24 ++++++++++ rpc/types/utils.go | 10 +++- rpc/websockets.go | 52 +++++++++++++++----- 10 files changed, 197 insertions(+), 34 deletions(-) diff --git a/changelog.md b/changelog.md index da4d082c95..1d85c97f86 100644 --- a/changelog.md +++ b/changelog.md @@ -59,6 +59,7 @@ * [2909](https://github.com/zeta-chain/node/pull/2909) - add legacy messages back to codec for querier backward compatibility * [3018](https://github.com/zeta-chain/node/pull/3018) - support `DepositAndCall` and `WithdrawAndCall` with empty payload * [3030](https://github.com/zeta-chain/node/pull/3030) - Avoid storing invalid Solana gateway address in the `SetGatewayAddress` +* [3047](https://github.com/zeta-chain/node/pull/3047) - wrong block hash in subscribe new heads ## v20.0.0 diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index a91118168a..60759d7e0a 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -404,8 +404,12 @@ func (b *Backend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Heade ) } - ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) - return ethHeader, nil + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + return rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee, validatorAccount), nil } // HeaderByHash returns the block header identified by hash. @@ -440,8 +444,12 @@ func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) ) } - ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) - return ethHeader, nil + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + return rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee, validatorAccount), nil } // BlockBloom query block bloom filter from block results @@ -613,7 +621,12 @@ func (b *Backend) EthBlockFromTendermintBlock( ) } - ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee) + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee, validatorAccount) msgs, additionals := b.EthMsgsFromTendermintBlock(resBlock, blockRes) txs := []*ethtypes.Transaction{} diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go index 5da81d03c9..8fdea01533 100644 --- a/rpc/backend/blocks_test.go +++ b/rpc/backend/blocks_test.go @@ -1191,6 +1191,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { var expResultBlock *tmrpctypes.ResultBlock _, bz := suite.buildEthereumTx() + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1218,6 +1219,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { height := blockNum.Int64() client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterBlockNotFound(client, height) + }, false, }, @@ -1245,6 +1247,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1260,6 +1263,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1275,6 +1279,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1287,7 +1292,12 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { header, err := suite.backend.HeaderByNumber(tc.blockNumber) if tc.expPass { - expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + expHeader := ethrpc.EthHeaderFromTendermint( + expResultBlock.Block.Header, + ethtypes.Bloom{}, + tc.baseFee, + validator, + ) suite.Require().NoError(err) suite.Require().Equal(expHeader, header) } else { @@ -1303,6 +1313,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { _, bz := suite.buildEthereumTx() block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1355,6 +1366,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1370,6 +1382,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1385,6 +1398,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1397,7 +1411,12 @@ func (suite *BackendTestSuite) TestHeaderByHash() { header, err := suite.backend.HeaderByHash(tc.hash) if tc.expPass { - expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + expHeader := ethrpc.EthHeaderFromTendermint( + expResultBlock.Block.Header, + ethtypes.Bloom{}, + tc.baseFee, + validator, + ) suite.Require().NoError(err) suite.Require().Equal(expHeader, header) } else { @@ -1410,6 +1429,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { func (suite *BackendTestSuite) TestEthBlockByNumber() { msgEthereumTx, bz := suite.buildEthereumTx() emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1453,12 +1473,14 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) baseFee := sdk.NewInt(1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{}, nil, @@ -1479,12 +1501,14 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) baseFee := sdk.NewInt(1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, nil, @@ -1520,6 +1544,7 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { msgEthereumTx, bz := suite.buildEthereumTx() emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1543,12 +1568,14 @@ func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { func(baseFee sdkmath.Int, blockNum int64) { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{}, nil, @@ -1578,12 +1605,14 @@ func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { func(baseFee sdkmath.Int, blockNum int64) { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, nil, @@ -1657,6 +1686,8 @@ func (suite *BackendTestSuite) TestEthAndSyntheticEthBlockByNumber() { msgEthereumTx, _ := suite.buildEthereumTx() realTx := suite.signAndEncodeEthTx(msgEthereumTx) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) + suite.backend.indexer = nil client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) @@ -1664,6 +1695,7 @@ func (suite *BackendTestSuite) TestEthAndSyntheticEthBlockByNumber() { RegisterBlock(client, 1, []tmtypes.Tx{realTx, tx}) RegisterBlockResultsWithTxResults(client, 1, []*types.ResponseDeliverTx{{}, &txRes}) RegisterBaseFee(queryClient, sdk.NewInt(1)) + RegisterValidatorAccount(queryClient, validator) // only real should be returned block, err := suite.backend.EthBlockByNumber(1) diff --git a/rpc/backend/call_tx_test.go b/rpc/backend/call_tx_test.go index ec92db089b..7d8d95275e 100644 --- a/rpc/backend/call_tx_test.go +++ b/rpc/backend/call_tx_test.go @@ -24,6 +24,7 @@ func (suite *BackendTestSuite) TestResend() { gasPrice := new(hexutil.Big) toAddr := tests.GenerateAddress() chainID := (*hexutil.Big)(suite.backend.chainID) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) callArgs := evmtypes.TransactionArgs{ From: nil, To: &toAddr, @@ -69,6 +70,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFeeDisabled(queryClient) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -91,6 +93,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -110,6 +113,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFeeDisabled(queryClient) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -163,6 +167,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -186,6 +191,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -210,6 +216,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) RegisterUnconfirmedTxsError(client, nil) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -238,6 +245,7 @@ func (suite *BackendTestSuite) TestResend() { RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) RegisterUnconfirmedTxsEmpty(client, nil) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -448,6 +456,7 @@ func (suite *BackendTestSuite) TestDoCall() { func (suite *BackendTestSuite) TestGasPrice() { defaultGasPrice := (*hexutil.Big)(big.NewInt(1)) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -467,6 +476,7 @@ func (suite *BackendTestSuite) TestGasPrice() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, sdk.NewInt(1)) + RegisterValidatorAccount(queryClient, validator) }, defaultGasPrice, true, @@ -483,6 +493,7 @@ func (suite *BackendTestSuite) TestGasPrice() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, sdk.NewInt(1)) + RegisterValidatorAccount(queryClient, validator) }, defaultGasPrice, false, diff --git a/rpc/backend/sign_tx_test.go b/rpc/backend/sign_tx_test.go index 1483b0b5e0..ee82303b0e 100644 --- a/rpc/backend/sign_tx_test.go +++ b/rpc/backend/sign_tx_test.go @@ -31,6 +31,7 @@ func (suite *BackendTestSuite) TestSendTransaction() { from := common.BytesToAddress(priv.PubKey().Address().Bytes()) nonce := hexutil.Uint64(1) baseFee := sdk.NewInt(1) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) callArgsDefault := evmtypes.TransactionArgs{ From: &from, To: &toAddr, @@ -82,6 +83,7 @@ func (suite *BackendTestSuite) TestSendTransaction() { RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, evmtypes.TransactionArgs{ From: &from, @@ -115,6 +117,7 @@ func (suite *BackendTestSuite) TestSendTransaction() { txBytes, err := txEncoder(tx) suite.Require().NoError(err) RegisterBroadcastTxError(client, txBytes) + RegisterValidatorAccount(queryClient, validator) }, callArgsDefault, common.Hash{}, @@ -142,6 +145,7 @@ func (suite *BackendTestSuite) TestSendTransaction() { txBytes, err := txEncoder(tx) suite.Require().NoError(err) RegisterBroadcastTx(client, txBytes) + RegisterValidatorAccount(queryClient, validator) }, callArgsDefault, hash, diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index bada8750c6..e297053fef 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -26,6 +26,7 @@ import ( "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/proto/tendermint/crypto" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" @@ -318,3 +319,16 @@ func GetHexProofs(proof *crypto.ProofOps) []string { } return proofs } + +func GetValidatorAccount(header *tmtypes.Header, qc *types.QueryClient) (sdk.AccAddress, error) { + res, err := qc.ValidatorAccount( + types.ContextWithHeight(header.Height), + &evmtypes.QueryValidatorAccountRequest{ + ConsAddress: sdk.ConsAddress(header.ProposerAddress).String(), + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to get validator account %w", err) + } + return sdk.AccAddressFromBech32(res.AccountAddress) +} diff --git a/rpc/namespaces/ethereum/eth/filters/api.go b/rpc/namespaces/ethereum/eth/filters/api.go index 7b6d3d9ff3..83a262b01f 100644 --- a/rpc/namespaces/ethereum/eth/filters/api.go +++ b/rpc/namespaces/ethereum/eth/filters/api.go @@ -27,11 +27,13 @@ import ( tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/node/rpc/backend" "github.com/zeta-chain/node/rpc/types" ) @@ -81,12 +83,13 @@ type filter struct { // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { - logger log.Logger - clientCtx client.Context - backend Backend - events *EventSystem - filtersMu sync.Mutex - filters map[rpc.ID]*filter + logger log.Logger + clientCtx client.Context + backend Backend + events *EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*filter + queryClient *types.QueryClient } // NewPublicAPI returns a new PublicFilterAPI instance. @@ -98,11 +101,12 @@ func NewPublicAPI( ) *PublicFilterAPI { logger = logger.With("api", "filter") api := &PublicFilterAPI{ - logger: logger, - clientCtx: clientCtx, - backend: backend, - filters: make(map[rpc.ID]*filter), - events: NewEventSystem(logger, tmWSClient), + logger: logger, + clientCtx: clientCtx, + backend: backend, + filters: make(map[rpc.ID]*filter), + events: NewEventSystem(logger, tmWSClient), + queryClient: types.NewQueryClient(clientCtx), } go api.timeoutLoop() @@ -368,9 +372,35 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events) + validatorAccount, err := backend.GetValidatorAccount(&data.Header, api.queryClient) + if err != nil { + api.logger.Error("failed to get validator account", "err", err) + continue + } + // TODO: fetch bloom from events - header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee) - err = notifier.Notify(rpcSub.ID, header) + header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee, validatorAccount) + + var enc types.Header + enc.ParentHash = header.ParentHash + enc.UncleHash = header.UncleHash + enc.Coinbase = header.Coinbase.Hex() + enc.Root = header.Root + enc.TxHash = header.TxHash + enc.ReceiptHash = header.ReceiptHash + enc.Bloom = header.Bloom + enc.Difficulty = (*hexutil.Big)(header.Difficulty) + enc.Number = (*hexutil.Big)(header.Number) + enc.GasLimit = hexutil.Uint64(header.GasLimit) + enc.GasUsed = hexutil.Uint64(header.GasUsed) + enc.Time = hexutil.Uint64(header.Time) + enc.Extra = header.Extra + enc.MixDigest = header.MixDigest + enc.Nonce = header.Nonce + enc.BaseFee = (*hexutil.Big)(header.BaseFee) + enc.Hash = common.BytesToHash(data.Header.Hash()) + + err = notifier.Notify(rpcSub.ID, enc) if err != nil { api.logger.Debug("failed to notify", "error", err.Error()) } diff --git a/rpc/types/block.go b/rpc/types/block.go index cb2c873709..ea1b872bcc 100644 --- a/rpc/types/block.go +++ b/rpc/types/block.go @@ -27,6 +27,7 @@ import ( grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/spf13/cast" ethermint "github.com/zeta-chain/ethermint/types" "google.golang.org/grpc/metadata" @@ -210,3 +211,26 @@ func (bnh *BlockNumberOrHash) decodeFromString(input string) error { } return nil } + +// https://github.com/ethereum/go-ethereum/blob/release/1.11/core/types/gen_header_json.go#L18 +type Header struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + // update string avoid lost checksumed miner after MarshalText + Coinbase string `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom ethtypes.Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce ethtypes.BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + // overwrite rlpHash + Hash common.Hash `json:"hash"` +} diff --git a/rpc/types/utils.go b/rpc/types/utils.go index addce9521a..67ee50133a 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -26,6 +26,7 @@ import ( tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -61,7 +62,12 @@ func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEth // EthHeaderFromTendermint is an util function that returns an Ethereum Header // from a tendermint Header. -func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header { +func EthHeaderFromTendermint( + header tmtypes.Header, + bloom ethtypes.Bloom, + baseFee *big.Int, + miner sdk.AccAddress, +) *ethtypes.Header { txHash := ethtypes.EmptyRootHash if len(header.DataHash) == 0 { txHash = common.BytesToHash(header.DataHash) @@ -70,7 +76,7 @@ func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFe return ðtypes.Header{ ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), UncleHash: ethtypes.EmptyUncleHash, - Coinbase: common.BytesToAddress(header.ProposerAddress), + Coinbase: common.BytesToAddress(miner), Root: common.BytesToHash(header.AppHash), TxHash: txHash, ReceiptHash: ethtypes.EmptyRootHash, diff --git a/rpc/websockets.go b/rpc/websockets.go index c977c41acc..7d60ae62a3 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -32,15 +32,16 @@ import ( tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/node/rpc/backend" "github.com/zeta-chain/node/rpc/ethereum/pubsub" rpcfilters "github.com/zeta-chain/node/rpc/namespaces/ethereum/eth/filters" "github.com/zeta-chain/node/rpc/types" @@ -378,18 +379,21 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro // pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec type pubSubAPI struct { - events *rpcfilters.EventSystem - logger log.Logger - clientCtx client.Context + events *rpcfilters.EventSystem + logger log.Logger + clientCtx client.Context + queryClient *types.QueryClient } // newPubSubAPI creates an instance of the ethereum PubSub API. func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { logger = logger.With("module", "websocket-client") + types.NewQueryClient(clientCtx) return &pubSubAPI{ - events: rpcfilters.NewEventSystem(logger, tmWSClient), - logger: logger, - clientCtx: clientCtx, + events: rpcfilters.NewEventSystem(logger, tmWSClient), + logger: logger, + clientCtx: clientCtx, + queryClient: types.NewQueryClient(clientCtx), } } @@ -423,9 +427,6 @@ func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.Un return nil, errors.Wrap(err, "error creating block filter") } - // TODO: use events - baseFee := big.NewInt(params.InitialBaseFee) - go func() { headersCh := sub.Event() errCh := sub.Err() @@ -442,7 +443,34 @@ func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.Un continue } - header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee) + validatorAccount, err := backend.GetValidatorAccount(&data.Header, api.queryClient) + if err != nil { + api.logger.Error("failed to get validator account", "err", err) + continue + } + + baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events) + + header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee, validatorAccount) + + var enc types.Header + enc.ParentHash = header.ParentHash + enc.UncleHash = header.UncleHash + enc.Coinbase = header.Coinbase.Hex() + enc.Root = header.Root + enc.TxHash = header.TxHash + enc.ReceiptHash = header.ReceiptHash + enc.Bloom = header.Bloom + enc.Difficulty = (*hexutil.Big)(header.Difficulty) + enc.Number = (*hexutil.Big)(header.Number) + enc.GasLimit = hexutil.Uint64(header.GasLimit) + enc.GasUsed = hexutil.Uint64(header.GasUsed) + enc.Time = hexutil.Uint64(header.Time) + enc.Extra = header.Extra + enc.MixDigest = header.MixDigest + enc.Nonce = header.Nonce + enc.BaseFee = (*hexutil.Big)(header.BaseFee) + enc.Hash = common.BytesToHash(data.Header.Hash()) // write to ws conn res := &SubscriptionNotification{ @@ -450,7 +478,7 @@ func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.Un Method: "eth_subscription", Params: &SubscriptionResult{ Subscription: subID, - Result: header, + Result: enc, }, } From ef764ae06f19bb5a01516ddd2316237164fe47da Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Mon, 28 Oct 2024 17:43:34 +0100 Subject: [PATCH 05/34] chore: fix changelog for v21 (#3052) --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 1d85c97f86..412141d2f8 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +## v21.0.0 + ### Features * [2633](https://github.com/zeta-chain/node/pull/2633) - support for stateful precompiled contracts From 8776a6a4d77d3d4ff0da25ce009595d78d6e8d21 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Mon, 28 Oct 2024 15:00:52 -0700 Subject: [PATCH 06/34] chore: update gosec (#2933) * chore: update gosec * Address gosec errors * update * fix spelling * disable sumdb in rpcimportable test to fix: reading https://sum.golang.org/lookup/github.com/zeta-chain/node@v0.0.0-20241028213324-8a6a543615d0: 500 Internal Server Error --- .github/workflows/ci.yml | 3 +- .github/workflows/sast-linters.yml | 2 +- cmd/zetaclientd/p2p_diagnostics.go | 1 + e2e/e2etests/test_bitcoin_std_deposit.go | 2 ++ e2e/runner/setup_solana.go | 3 +- pkg/contracts/ton/gateway.go | 1 + pkg/crypto/aes256_gcm.go | 1 + pkg/memo/codec_compact.go | 1 + pkg/ticker/ticker.go | 7 ++-- precompiles/logs/logs.go | 7 ++-- precompiles/staking/staking.go | 5 +-- rpc/backend/node_info.go | 4 ++- rpc/backend/tx_info.go | 6 ++-- rpc/namespaces/ethereum/debug/api.go | 8 +++++ rpc/types/utils.go | 32 +++++++++++-------- scripts/gosec.sh | 2 +- zetaclient/chains/bitcoin/observer/inbound.go | 1 + zetaclient/chains/evm/observer/inbound.go | 4 +-- zetaclient/chains/evm/observer/outbound.go | 1 + zetaclient/chains/ton/observer/inbound.go | 4 +-- zetaclient/orchestrator/orchestrator.go | 2 +- zetaclient/types/dynamic_ticker.go | 4 ++- zetaclient/zetacore/client_worker.go | 1 + 23 files changed, 67 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 518823b42f..741931df27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,8 +105,9 @@ jobs: - name: go get node working-directory: contrib/rpcimportable run: go get github.com/zeta-chain/node@${{github.event.pull_request.head.sha || github.sha}} - env: + env: GOPROXY: direct + GOSUMDB: off - name: go mod tidy working-directory: contrib/rpcimportable run: go mod tidy diff --git a/.github/workflows/sast-linters.yml b/.github/workflows/sast-linters.yml index 26c8fb895d..7b09472298 100644 --- a/.github/workflows/sast-linters.yml +++ b/.github/workflows/sast-linters.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Run Gosec Security Scanner - uses: zeta-chain/gosec@v2.21.0-zeta + uses: zeta-chain/gosec@v2.21.4-zeta2 with: args: -exclude-generated -exclude-dir testutil ./... diff --git a/cmd/zetaclientd/p2p_diagnostics.go b/cmd/zetaclientd/p2p_diagnostics.go index 201b91504b..b041c4993b 100644 --- a/cmd/zetaclientd/p2p_diagnostics.go +++ b/cmd/zetaclientd/p2p_diagnostics.go @@ -151,6 +151,7 @@ func RunDiagnostics( startLogger.Info().Msgf("Successfully announced!") // every 1min, print out the p2p diagnostic + // #nosec G115 interval is in range and not user controlled ticker := time.NewTicker(time.Duration(cfg.P2PDiagnosticTicker) * time.Second) round := 0 diff --git a/e2e/e2etests/test_bitcoin_std_deposit.go b/e2e/e2etests/test_bitcoin_std_deposit.go index fa123b94cd..e90b23d64a 100644 --- a/e2e/e2etests/test_bitcoin_std_deposit.go +++ b/e2e/e2etests/test_bitcoin_std_deposit.go @@ -59,5 +59,7 @@ func TestBitcoinStdMemoDeposit(r *runner.E2ERunner, args []string) { amountIncreased := new(big.Int).Sub(balanceAfter, balanceBefore) amountSatoshis, err := bitcoin.GetSatoshis(amount) require.NoError(r, err) + require.Positive(r, amountSatoshis) + // #nosec G115 always positive require.Equal(r, uint64(amountSatoshis), amountIncreased.Uint64()) } diff --git a/e2e/runner/setup_solana.go b/e2e/runner/setup_solana.go index 17bf3149dd..b8bb309ba1 100644 --- a/e2e/runner/setup_solana.go +++ b/e2e/runner/setup_solana.go @@ -56,7 +56,8 @@ func (r *E2ERunner) SetupSolana(deployerPrivateKey string) { inst.DataBytes, err = borsh.Serialize(solanacontracts.InitializeParams{ Discriminator: solanacontracts.DiscriminatorInitialize(), TssAddress: r.TSSAddress, - ChainID: uint64(chains.SolanaLocalnet.ChainId), + // #nosec G115 chain id always positive + ChainID: uint64(chains.SolanaLocalnet.ChainId), }) require.NoError(r, err) diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go index 33ca2c7977..ab21a41ba3 100644 --- a/pkg/contracts/ton/gateway.go +++ b/pkg/contracts/ton/gateway.go @@ -124,6 +124,7 @@ func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) { var ( sender = *sourceID + // #nosec G115 always in range opCode = Op(op) content any diff --git a/pkg/crypto/aes256_gcm.go b/pkg/crypto/aes256_gcm.go index e4fba7de7c..a7538363da 100644 --- a/pkg/crypto/aes256_gcm.go +++ b/pkg/crypto/aes256_gcm.go @@ -103,6 +103,7 @@ func DecryptAES256GCM(ciphertext []byte, password string) ([]byte, error) { nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] // decrypt the ciphertext + // #nosec G407 false positive https://github.com/securego/gosec/issues/1211 plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return nil, err diff --git a/pkg/memo/codec_compact.go b/pkg/memo/codec_compact.go index bcb49d3f92..817a12066a 100644 --- a/pkg/memo/codec_compact.go +++ b/pkg/memo/codec_compact.go @@ -120,6 +120,7 @@ func (c *CodecCompact) packLength(length int) ([]byte, error) { if length > math.MaxUint8 { return nil, fmt.Errorf("data length %d exceeds %d bytes", length, math.MaxUint8) } + // #nosec G115 range checked data[0] = uint8(length) case LenBytesLong: if length > math.MaxUint16 { diff --git a/pkg/ticker/ticker.go b/pkg/ticker/ticker.go index 2a5a7edff1..94d4a87efc 100644 --- a/pkg/ticker/ticker.go +++ b/pkg/ticker/ticker.go @@ -183,7 +183,8 @@ func (t *Ticker) Stop() { t.logger.Info().Msgf("Ticker stopped") } -// SecondsFromUint64 converts uint64 to time.Duration in seconds. -func SecondsFromUint64(d uint64) time.Duration { - return time.Duration(d) * time.Second +// DurationFromUint64Seconds converts uint64 of seconds to time.Duration. +func DurationFromUint64Seconds(seconds uint64) time.Duration { + // #nosec G115 seconds should be in range and is not user controlled + return time.Duration(seconds) * time.Second } diff --git a/precompiles/logs/logs.go b/precompiles/logs/logs.go index 07960a442e..6848cbc9a0 100644 --- a/precompiles/logs/logs.go +++ b/precompiles/logs/logs.go @@ -16,9 +16,10 @@ type Argument struct { // AddLog adds log to stateDB func AddLog(ctx sdk.Context, precompileAddr common.Address, stateDB vm.StateDB, topics []common.Hash, data []byte) { stateDB.AddLog(&types.Log{ - Address: precompileAddr, - Topics: topics, - Data: data, + Address: precompileAddr, + Topics: topics, + Data: data, + // #nosec G115 block height always positive BlockNumber: uint64(ctx.BlockHeight()), }) } diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index fea0d0f9f2..d29082e545 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -133,8 +133,9 @@ func (c *Contract) GetAllValidators( validatorsRes[i] = Validator{ OperatorAddress: v.OperatorAddress, ConsensusPubKey: v.ConsensusPubkey.String(), - BondStatus: uint8(v.Status), - Jailed: v.Jailed, + // #nosec G115 enum always in range + BondStatus: uint8(v.Status), + Jailed: v.Jailed, } } diff --git a/rpc/backend/node_info.go b/rpc/backend/node_info.go index 0a5c9c0b0b..180389298e 100644 --- a/rpc/backend/node_info.go +++ b/rpc/backend/node_info.go @@ -81,8 +81,10 @@ func (b *Backend) Syncing() (interface{}, error) { } return map[string]interface{}{ + // #nosec G115 block height always positive "startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight), - "currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight), + // #nosec G115 block height always positive + "currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight), // "highestBlock": nil, // NA // "pulledStates": nil, // NA // "knownStates": nil, // NA diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 3d2810331d..3f69ec86f9 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -304,8 +304,10 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. - "blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(), - "blockNumber": hexutil.Uint64(res.Height), + "blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(), + // #nosec G115 height always positive + "blockNumber": hexutil.Uint64(res.Height), + // #nosec G115 tx index always positive "transactionIndex": hexutil.Uint64(res.EthTxIndex), // sender and receiver (contract or EOA) addreses diff --git a/rpc/namespaces/ethereum/debug/api.go b/rpc/namespaces/ethereum/debug/api.go index 27c4ec5500..39288a125f 100644 --- a/rpc/namespaces/ethereum/debug/api.go +++ b/rpc/namespaces/ethereum/debug/api.go @@ -126,6 +126,7 @@ func (a *API) BlockProfile(file string, nsec uint) error { runtime.SetBlockProfileRate(1) defer runtime.SetBlockProfileRate(0) + // #nosec G115 uint always in int64 range time.Sleep(time.Duration(nsec) * time.Second) return writeProfile("block", file, a.logger) } @@ -137,6 +138,7 @@ func (a *API) CpuProfile(file string, nsec uint) error { //nolint: golint, style if err := a.StartCPUProfile(file); err != nil { return err } + // #nosec G115 uint always in int64 range time.Sleep(time.Duration(nsec) * time.Second) return a.StopCPUProfile() } @@ -156,6 +158,7 @@ func (a *API) GoTrace(file string, nsec uint) error { if err := a.StartGoTrace(file); err != nil { return err } + // #nosec G115 uint always in int64 range time.Sleep(time.Duration(nsec) * time.Second) return a.StopGoTrace() } @@ -273,6 +276,7 @@ func (a *API) WriteMemProfile(file string) error { func (a *API) MutexProfile(file string, nsec uint) error { a.logger.Debug("debug_mutexProfile", "file", file, "nsec", nsec) runtime.SetMutexProfileFraction(1) + // #nosec G115 uint always in int64 range time.Sleep(time.Duration(nsec) * time.Second) defer runtime.SetMutexProfileFraction(0) return writeProfile("mutex", file, a.logger) @@ -305,6 +309,7 @@ func (a *API) SetGCPercent(v int) int { // GetHeaderRlp retrieves the RLP encoded for of a single header. func (a *API) GetHeaderRlp(number uint64) (hexutil.Bytes, error) { + // #nosec G115 number always in int64 range header, err := a.backend.HeaderByNumber(rpctypes.BlockNumber(number)) if err != nil { return nil, err @@ -315,6 +320,7 @@ func (a *API) GetHeaderRlp(number uint64) (hexutil.Bytes, error) { // GetBlockRlp retrieves the RLP encoded for of a single block. func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) { + // #nosec G115 number always in int64 range block, err := a.backend.EthBlockByNumber(rpctypes.BlockNumber(number)) if err != nil { return nil, err @@ -325,6 +331,7 @@ func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) { // PrintBlock retrieves a block and returns its pretty printed form. func (a *API) PrintBlock(number uint64) (string, error) { + // #nosec G115 number always in int64 range block, err := a.backend.EthBlockByNumber(rpctypes.BlockNumber(number)) if err != nil { return "", err @@ -335,6 +342,7 @@ func (a *API) PrintBlock(number uint64) (string, error) { // SeedHash retrieves the seed hash of a block. func (a *API) SeedHash(number uint64) (string, error) { + // #nosec G115 number always in int64 range _, err := a.backend.HeaderByNumber(rpctypes.BlockNumber(number)) if err != nil { return "", err diff --git a/rpc/types/utils.go b/rpc/types/utils.go index 67ee50133a..a17f3af2a2 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -133,20 +133,24 @@ func FormatBlock( } result := map[string]interface{}{ - "number": hexutil.Uint64(header.Height), - "hash": hexutil.Bytes(header.Hash()), - "parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()), - "nonce": ethtypes.BlockNonce{}, // PoW specific - "sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint - "logsBloom": bloom, - "stateRoot": hexutil.Bytes(header.AppHash), - "miner": validatorAddr, - "mixHash": common.Hash{}, - "difficulty": (*hexutil.Big)(big.NewInt(0)), - "extraData": "0x", - "size": hexutil.Uint64(size), - "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit - "gasUsed": (*hexutil.Big)(gasUsed), + // #nosec G115 block height always positive + "number": hexutil.Uint64(header.Height), + "hash": hexutil.Bytes(header.Hash()), + "parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()), + "nonce": ethtypes.BlockNonce{}, // PoW specific + "sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint + "logsBloom": bloom, + "stateRoot": hexutil.Bytes(header.AppHash), + "miner": validatorAddr, + "mixHash": common.Hash{}, + "difficulty": (*hexutil.Big)(big.NewInt(0)), + "extraData": "0x", + // #nosec G115 size always positive + "size": hexutil.Uint64(size), + // #nosec G115 gasLimit always positive + "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit + "gasUsed": (*hexutil.Big)(gasUsed), + // #nosec G115 timestamp always positive "timestamp": hexutil.Uint64(header.Time.Unix()), "transactionsRoot": transactionsRoot, "receiptsRoot": ethtypes.EmptyRootHash, diff --git a/scripts/gosec.sh b/scripts/gosec.sh index 2b831a35e9..cfc2d603df 100644 --- a/scripts/gosec.sh +++ b/scripts/gosec.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -docker run -it --rm -w /node -v "$(pwd):/node" ghcr.io/zeta-chain/gosec:2.21.0-zeta -exclude-generated -exclude-dir testutil ./... \ No newline at end of file +docker run -it --rm -w /node -v "$(pwd):/node" ghcr.io/zeta-chain/gosec:2.21.4-zeta2 -exclude-generated -exclude-dir testutil ./... diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index b08fbea18c..3cd1ab3945 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -250,6 +250,7 @@ func (ob *Observer) CheckReceiptForBtcTxHash(ctx context.Context, txHash string, } // check confirmation + // #nosec G115 block height always positive if !ob.IsBlockConfirmed(uint64(blockVb.Height)) { return "", fmt.Errorf("block %d is not confirmed yet", blockVb.Height) } diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index 02ab1a0b6f..cb409f408e 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -38,7 +38,7 @@ import ( // TODO(revamp): move ticker function to a separate file func (ob *Observer) WatchInbound(ctx context.Context) error { sampledLogger := ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10}) - interval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker) + interval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) task := func(ctx context.Context, t *ticker.Ticker) error { return ob.watchInboundOnce(ctx, t, sampledLogger) } @@ -70,7 +70,7 @@ func (ob *Observer) watchInboundOnce(ctx context.Context, t *ticker.Ticker, samp ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error") } - newInterval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker) + newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) t.SetInterval(newInterval) return nil diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 0bab913592..f8ce8f32ba 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -450,6 +450,7 @@ func (ob *Observer) FilterTSSOutboundInBlock(ctx context.Context, blockNumber ui for i := range block.Transactions { tx := block.Transactions[i] if ethcommon.HexToAddress(tx.From) == ob.TSS().EVMAddress() { + // #nosec G115 nonce always positive nonce := uint64(tx.Nonce) if !ob.IsTxConfirmed(nonce) { if receipt, txx, ok := ob.checkConfirmedTx(ctx, tx.Hash, nonce); ok { diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go index 95f9a510d7..9a5e6ba548 100644 --- a/zetaclient/chains/ton/observer/inbound.go +++ b/zetaclient/chains/ton/observer/inbound.go @@ -31,7 +31,7 @@ func (ob *Observer) watchInbound(ctx context.Context) error { var ( chainID = ob.Chain().ChainId - initialInterval = ticker.SecondsFromUint64(ob.ChainParams().InboundTicker) + initialInterval = ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) sampledLogger = ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10}) ) @@ -47,7 +47,7 @@ func (ob *Observer) watchInbound(ctx context.Context) error { ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error") } - newInterval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker) + newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) t.SetInterval(newInterval) return nil diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 698a520fcf..b63b7e3a00 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -618,7 +618,7 @@ func (oc *Orchestrator) ScheduleCctxSolana( oc.logger.Error().Msgf("ScheduleCctxSolana: chain observer is not a solana observer") return } - // #nosec G701 positive + // #nosec G115 positive interval := uint64(observer.ChainParams().OutboundScheduleInterval) // schedule keysign for each pending cctx diff --git a/zetaclient/types/dynamic_ticker.go b/zetaclient/types/dynamic_ticker.go index 103bfffb94..bfbc1e3cd4 100644 --- a/zetaclient/types/dynamic_ticker.go +++ b/zetaclient/types/dynamic_ticker.go @@ -23,7 +23,8 @@ func NewDynamicTicker(name string, interval uint64) (*DynamicTicker, error) { return &DynamicTicker{ name: name, interval: interval, - impl: time.NewTicker(time.Duration(interval) * time.Second), + // #nosec G115 interval is in range and not user controlled + impl: time.NewTicker(time.Duration(interval) * time.Second), }, nil } @@ -38,6 +39,7 @@ func (t *DynamicTicker) UpdateInterval(newInterval uint64, logger zerolog.Logger t.impl.Stop() oldInterval := t.interval t.interval = newInterval + // #nosec G115 interval is in range and not user controlled t.impl = time.NewTicker(time.Duration(t.interval) * time.Second) logger.Info().Msgf("%s ticker interval changed from %d to %d", t.name, oldInterval, newInterval) } diff --git a/zetaclient/zetacore/client_worker.go b/zetaclient/zetacore/client_worker.go index c5dcaa248d..b1fb4a6074 100644 --- a/zetaclient/zetacore/client_worker.go +++ b/zetaclient/zetacore/client_worker.go @@ -21,6 +21,7 @@ func (c *Client) UpdateAppContextWorker(ctx context.Context, app *appcontext.App }() var ( + // #nosec G115 interval is in range and not user controlled updateEvery = time.Duration(app.Config().ConfigUpdateTicker) * time.Second ticker = time.NewTicker(updateEvery) logger = c.logger.Sample(logSampler) From b3e903d84a30170edbe875e439fe0e254a33b802 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 29 Oct 2024 05:15:46 -0400 Subject: [PATCH 07/34] fix: remove additional log line when exporting genesis (#3038) * remove additional log line when exporting genesis * modify comments on appExport * rename identifiers * add comments * remove unnecessary new line * Update cmd/zetacored/root.go Co-authored-by: Lucas Bertrand * use loadlatest flag for loading app for height --------- Co-authored-by: Lucas Bertrand --- app/app.go | 2 -- app/export.go | 1 - cmd/zetacored/root.go | 53 ++++++++++++++++++++----------------------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/app/app.go b/app/app.go index 1e64321f21..3a42d51aff 100644 --- a/app/app.go +++ b/app/app.go @@ -372,8 +372,6 @@ func New( authAddr, ) - logger.Info("bank keeper blocklist addresses", "addresses", app.BlockedAddrs()) - app.BankKeeper = bankkeeper.NewBaseKeeper( appCodec, keys[banktypes.StoreKey], diff --git a/app/export.go b/app/export.go index 1f1ae26a96..61bc52659b 100644 --- a/app/export.go +++ b/app/export.go @@ -17,7 +17,6 @@ import ( func (app *App) ExportAppStateAndValidators( forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, ) (servertypes.ExportedApp, error) { - // as if they could withdraw from the start of the next block ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) // We export at last height + 1, because that's the height at which diff --git a/cmd/zetacored/root.go b/cmd/zetacored/root.go index 440b921bc2..3880b12108 100644 --- a/cmd/zetacored/root.go +++ b/cmd/zetacored/root.go @@ -268,48 +268,43 @@ func (ac appCreator) newApp( ) } -// appExport creates a new simapp (optionally at a given height) +// appExport is used to export the state of the application for a genesis file. func (ac appCreator) appExport( logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, appOpts servertypes.AppOptions, modulesToExport []string, ) (servertypes.ExportedApp, error) { - var anApp *app.App + var zetaApp *app.App homePath, ok := appOpts.Get(flags.FlagHome).(string) if !ok || homePath == "" { return servertypes.ExportedApp{}, errors.New("application home not set") } - if height != -1 { - anApp = app.New( - logger, - db, - traceStore, - false, - map[int64]bool{}, - homePath, - uint(1), - ac.encCfg, - appOpts, - ) - - if err := anApp.LoadHeight(height); err != nil { + loadLatest := false + if height == -1 { + loadLatest = true + } + + zetaApp = app.New( + logger, + db, + traceStore, + loadLatest, + map[int64]bool{}, + homePath, + uint(1), + ac.encCfg, + appOpts, + ) + + // If height is -1, it means we are using the latest height. + // For all other cases, we load the specified height from the Store + if !loadLatest { + if err := zetaApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } - } else { - anApp = app.New( - logger, - db, - traceStore, - true, - map[int64]bool{}, - homePath, - uint(1), - ac.encCfg, - appOpts, - ) } - return anApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) + return zetaApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) } From d9ee4f395da0bfdaa8dbc35cf1a876ebb080af36 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Tue, 29 Oct 2024 11:54:15 +0100 Subject: [PATCH 08/34] fix(e2e): wrong evm client used for TestDAppV2ZEVM (#3060) --- cmd/zetae2e/config/contracts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/zetae2e/config/contracts.go b/cmd/zetae2e/config/contracts.go index 9af3ccd812..6f2dff72c6 100644 --- a/cmd/zetae2e/config/contracts.go +++ b/cmd/zetae2e/config/contracts.go @@ -277,7 +277,7 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error { if err != nil { return fmt.Errorf("invalid TestDAppV2Addr: %w", err) } - r.TestDAppV2ZEVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2ZEVMAddr, r.EVMClient) + r.TestDAppV2ZEVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2ZEVMAddr, r.ZEVMClient) if err != nil { return err } From 9b285c2694f672073f5927608ca799f164859126 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 29 Oct 2024 09:07:39 -0700 Subject: [PATCH 09/34] chore: upgrade cometbft to v0.37.12 (#3045) * chore: upgrade cometbft to v0.37.12 * upgrade to go 1.22.7 --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- Dockerfile-localnet | 6 +- Makefile | 2 +- .../orchestrator/Dockerfile.fastbuild | 2 +- go.mod | 117 ++++----- go.sum | 222 ++++++++++-------- 5 files changed, 185 insertions(+), 164 deletions(-) diff --git a/Dockerfile-localnet b/Dockerfile-localnet index 0ec2408120..49247d6be4 100644 --- a/Dockerfile-localnet +++ b/Dockerfile-localnet @@ -1,6 +1,6 @@ # syntax=ghcr.io/zeta-chain/docker-dockerfile:1.9-labs # check=error=true -FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm AS base-build +FROM ghcr.io/zeta-chain/golang:1.22.7-bookworm AS base-build ENV GOPATH=/go ENV GOOS=linux @@ -27,10 +27,10 @@ RUN --mount=type=cache,target="/root/.cache/go-build" \ NODE_COMMIT=${NODE_COMMIT} \ make install install-zetae2e -FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm AS cosmovisor-build +FROM ghcr.io/zeta-chain/golang:1.22.7-bookworm AS cosmovisor-build RUN go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.6.0 -FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm AS base-runtime +FROM ghcr.io/zeta-chain/golang:1.22.7-bookworm AS base-runtime RUN apt update && \ apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 bind9-host && \ diff --git a/Makefile b/Makefile index 1e87c984e9..8ee617a9d2 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DOCKER ?= docker DOCKER_COMPOSE ?= $(DOCKER) compose -f docker-compose.yml $(NODE_COMPOSE_ARGS) DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf GOFLAGS := "" -GOLANG_CROSS_VERSION ?= v1.22.4 +GOLANG_CROSS_VERSION ?= v1.22.7 GOPATH ?= '$(HOME)/go' ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=zetacore \ diff --git a/contrib/localnet/orchestrator/Dockerfile.fastbuild b/contrib/localnet/orchestrator/Dockerfile.fastbuild index c776d96c06..cbab881a65 100644 --- a/contrib/localnet/orchestrator/Dockerfile.fastbuild +++ b/contrib/localnet/orchestrator/Dockerfile.fastbuild @@ -3,7 +3,7 @@ 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 +FROM ghcr.io/zeta-chain/golang:1.22.7-bookworm AS orchestrator RUN apt update && \ apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 bind9-host && \ diff --git a/go.mod b/go.mod index 524a63d8be..8e534257d5 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/zeta-chain/node -go 1.22.2 +go 1.22.7 -toolchain go1.22.5 +toolchain go1.22.8 require ( cosmossdk.io/errors v1.0.1 @@ -14,10 +14,10 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/cockroachdb/errors v1.11.1 + github.com/cockroachdb/errors v1.11.3 github.com/coinbase/rosetta-sdk-go v0.7.9 - github.com/cometbft/cometbft v0.37.5 - github.com/cometbft/cometbft-db v0.12.0 + github.com/cometbft/cometbft v0.37.12 + github.com/cometbft/cometbft-db v0.14.1 github.com/cosmos/btcutil v1.0.5 github.com/cosmos/cosmos-sdk v0.47.14 github.com/cosmos/gogoproto v1.7.0 @@ -31,7 +31,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.3 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-getter v1.7.5 github.com/huandu/skiplist v1.2.0 @@ -46,29 +46,29 @@ require ( github.com/near/borsh-go v0.3.1 github.com/onrik/ethrpc v1.2.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.20.4 github.com/rakyll/statik v0.1.7 - github.com/rs/cors v1.8.3 + github.com/rs/cors v1.11.1 github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.46.0 github.com/spf13/afero v1.11.0 github.com/spf13/cast v1.6.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7 github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c gitlab.com/thorchain/tss/go-tss v1.6.5 go.nhat.io/grpcmock v0.25.0 - golang.org/x/crypto v0.23.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/net v0.25.0 - golang.org/x/sync v0.7.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 - google.golang.org/grpc v1.62.1 - google.golang.org/protobuf v1.33.0 + golang.org/x/crypto v0.27.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/net v0.29.0 + golang.org/x/sync v0.8.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 + google.golang.org/grpc v1.67.0 + google.golang.org/protobuf v1.34.2 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -77,18 +77,17 @@ require ( ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect - cloud.google.com/go/storage v1.36.0 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/storage v1.38.0 // indirect cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect cosmossdk.io/log v1.4.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect + github.com/ChainSafe/go-schnorrkel v1.1.0 // indirect github.com/DataDog/zstd v1.5.0 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect @@ -106,11 +105,11 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.0 // indirect + github.com/cockroachdb/pebble v1.1.1 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/confio/ics23/go v0.9.0 // indirect @@ -123,8 +122,8 @@ require ( github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect - github.com/creachadair/taskgroup v0.4.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/creachadair/taskgroup v0.10.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set v1.8.0 // indirect @@ -146,11 +145,11 @@ require ( github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect - github.com/getsentry/sentry-go v0.23.0 // indirect - github.com/go-kit/kit v0.12.0 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect @@ -159,10 +158,10 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang/glog v1.2.0 // indirect + github.com/golang/glog v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gopacket v1.1.19 // indirect @@ -170,7 +169,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect @@ -202,12 +201,12 @@ require ( 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 - github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.7 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect @@ -227,13 +226,12 @@ 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/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect - github.com/minio/highwayhash v1.0.2 // indirect + github.com/minio/highwayhash v1.0.3 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -258,21 +256,21 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/otiai10/primes v0.0.0-20180210170552-f6d2a1ba97c4 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/tsdb v0.7.1 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rjeczalik/notify v0.9.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/status-im/keycard-go v0.2.0 // indirect @@ -303,9 +301,9 @@ require ( go.nhat.io/matcher/v2 v2.0.0 // indirect go.nhat.io/wait v0.1.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/fx v1.19.2 // indirect @@ -313,17 +311,16 @@ require ( go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.155.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + gonum.org/v1/gonum v0.15.1 // indirect + google.golang.org/api v0.171.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect lukechampine.com/blake3 v1.2.1 // indirect @@ -339,14 +336,18 @@ require ( ) require ( + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/snksoft/crc v1.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect ) replace ( diff --git a/go.sum b/go.sum index 04150048bc..abdc26e148 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5 cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -326,13 +326,13 @@ cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -630,8 +630,9 @@ cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+K cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -1058,8 +1059,8 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1315,8 +1316,9 @@ github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= +github.com/ChainSafe/go-schnorrkel v1.1.0 h1:rZ6EU+CZFCjB4sHUE1jIu8VDoB/wRKZxoe1tkcO71Wk= +github.com/ChainSafe/go-schnorrkel v1.1.0/go.mod h1:ABkENxiP+cvjFiByMIZ9LYbRoNNLeBLiakC1XeTFxfE= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= @@ -1415,8 +1417,9 @@ github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= -github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -1649,8 +1652,9 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4/go.mod h1:W8EnPSQ8Nv4fUjc/v1/8tHFqhuOJXnRub0dTfuAQktU= github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To= @@ -1698,8 +1702,6 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4= @@ -1714,15 +1716,17 @@ github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHy github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= -github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= -github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677/go.mod h1:890yq1fUb9b6dGNwssgeUO5vQV9qfXnCPxAJhBQfXw0= -github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= -github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= +github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= @@ -1739,11 +1743,11 @@ github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= github.com/cometbft/cometbft v0.34.27-alpha.1/go.mod h1:hct3hasQ2hIF3HoD7foVw4RaqTNSSeJ/lgcrVK6uDvs= github.com/cometbft/cometbft v0.37.4/go.mod h1:Cmg5Hp4sNpapm7j+x0xRyt2g0juQfmB752ous+pA0G8= -github.com/cometbft/cometbft v0.37.5 h1:/U/TlgMh4NdnXNo+YU9T2NMCWyhXNDF34Mx582jlvq0= -github.com/cometbft/cometbft v0.37.5/go.mod h1:QC+mU0lBhKn8r9qvmnq53Dmf3DWBt4VtkcKw2C81wxY= +github.com/cometbft/cometbft v0.37.12 h1:gHrZAJPM+KOzCreP4y6KHOHu7HzHJT0fBG/aO7QvCzI= +github.com/cometbft/cometbft v0.37.12/go.mod h1:lvHwNAsn6R1tKnRhHvRMvL40efsZFbHpvEH/3KHoQI8= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= -github.com/cometbft/cometbft-db v0.12.0 h1:v77/z0VyfSU7k682IzZeZPFZrQAKiQwkqGN0QzAjMi0= -github.com/cometbft/cometbft-db v0.12.0/go.mod h1:aX2NbCrjNVd2ZajYxt1BsiFf/Z+TQ2MN0VxdicheYuw= +github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= +github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= @@ -1940,11 +1944,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= -github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= -github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= +github.com/creachadair/taskgroup v0.10.0 h1:xCz5Kk9PU3UL2VNF7XJ2MnIxIw48hUwYVLwtTzMzqtE= +github.com/creachadair/taskgroup v0.10.0/go.mod h1:9oDDPt/5QPS4iylvPMC81GRlj+1je8AFDbjUh4zaQWo= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -2118,8 +2122,6 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= @@ -2203,8 +2205,9 @@ github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TB github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -2248,8 +2251,9 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -2271,8 +2275,8 @@ github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= @@ -2398,8 +2402,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -2478,8 +2482,9 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= @@ -2602,8 +2607,9 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -2639,8 +2645,9 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= @@ -2758,6 +2765,8 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -3012,8 +3021,8 @@ github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -3051,6 +3060,7 @@ github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfR github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= @@ -3077,8 +3087,9 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -3208,7 +3219,6 @@ github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb44 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= @@ -3251,8 +3261,9 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcs github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= @@ -3363,6 +3374,7 @@ github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= @@ -3510,8 +3522,9 @@ github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04s github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -3574,16 +3587,17 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= @@ -3644,8 +3658,9 @@ github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -3654,8 +3669,9 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -3673,8 +3689,9 @@ github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -3691,8 +3708,9 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= @@ -3747,8 +3765,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= -github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -3789,8 +3807,9 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= -github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.13.0/go.mod h1:D2Wb7niIYmTB+gB8z7kh8tyP5ccof1dQ+SFk+WW5NtY= github.com/sashamelentyev/usestdlibvars v1.20.0/go.mod h1:0GaP+ecfZMXShS0A94CJn6aEuPRILv8h/VuWI9n1ygg= @@ -3814,8 +3833,8 @@ github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= @@ -3904,8 +3923,8 @@ github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -3925,8 +3944,8 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= @@ -4286,13 +4305,13 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.2 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0/go.mod h1:LsankqVDx4W+RhZNA5uWarULII/MBhF5qwCYxTuyXjs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.33.0/go.mod h1:y/SlJpJQPd2UzfBCj0E9Flk9FDCtTyqUmaCB41qFrWI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3/go.mod h1:Dts42MGkzZne2yCru741+bFiTMWkIj/LLRizad7b9tw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0/go.mod h1:vHItvsnJtp7ES++nFLLFBzUWny7fJQSvTlxFcqQGUr4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= @@ -4300,8 +4319,8 @@ go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdT go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= @@ -4317,15 +4336,15 @@ go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9deb go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= go.opentelemetry.io/otel/metric v0.32.3/go.mod h1:pgiGmKohxHyTPHGOff+vrtIH39/R9fiO/WoenUQ3kcc= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= @@ -4335,8 +4354,8 @@ go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwY go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= @@ -4460,8 +4479,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -4487,8 +4506,8 @@ golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZ golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -4664,8 +4683,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -4707,8 +4726,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -4731,8 +4750,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -4940,8 +4959,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -4965,8 +4985,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -4989,8 +5009,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -5163,8 +5183,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= @@ -5172,8 +5193,8 @@ gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -5261,8 +5282,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -5273,7 +5294,6 @@ google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -5464,8 +5484,8 @@ google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -5485,8 +5505,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go. google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -5511,8 +5531,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -5578,8 +5598,8 @@ google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSs google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -5605,8 +5625,8 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 9ef40093f63e411ce167535afcf94a7f94625f68 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 29 Oct 2024 13:22:39 -0700 Subject: [PATCH 10/34] chore: revert cometbft upgrade (#3065) * Revert "chore: upgrade cometbft to v0.37.12 (#3045)" This reverts commit 9b285c2694f672073f5927608ca799f164859126. * only revert go.mod changes --- go.mod | 113 +++++++++++++++-------------- go.sum | 222 ++++++++++++++++++++++++++------------------------------- 2 files changed, 157 insertions(+), 178 deletions(-) diff --git a/go.mod b/go.mod index 8e534257d5..8ed2cb3548 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,10 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/cockroachdb/errors v1.11.3 + github.com/cockroachdb/errors v1.11.1 github.com/coinbase/rosetta-sdk-go v0.7.9 - github.com/cometbft/cometbft v0.37.12 - github.com/cometbft/cometbft-db v0.14.1 + github.com/cometbft/cometbft v0.37.5 + github.com/cometbft/cometbft-db v0.12.0 github.com/cosmos/btcutil v1.0.5 github.com/cosmos/cosmos-sdk v0.47.14 github.com/cosmos/gogoproto v1.7.0 @@ -31,7 +31,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-getter v1.7.5 github.com/huandu/skiplist v1.2.0 @@ -46,29 +46,29 @@ require ( github.com/near/borsh-go v0.3.1 github.com/onrik/ethrpc v1.2.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.20.4 + github.com/prometheus/client_golang v1.14.0 github.com/rakyll/statik v0.1.7 - github.com/rs/cors v1.11.1 + github.com/rs/cors v1.8.3 github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.46.0 github.com/spf13/afero v1.11.0 github.com/spf13/cast v1.6.0 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.19.0 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7 github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c gitlab.com/thorchain/tss/go-tss v1.6.5 go.nhat.io/grpcmock v0.25.0 - golang.org/x/crypto v0.27.0 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/net v0.29.0 - golang.org/x/sync v0.8.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 - google.golang.org/grpc v1.67.0 - google.golang.org/protobuf v1.34.2 + golang.org/x/crypto v0.23.0 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.7.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 + google.golang.org/grpc v1.62.1 + google.golang.org/protobuf v1.33.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -77,17 +77,18 @@ require ( ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/storage v1.38.0 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/storage v1.36.0 // indirect cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect cosmossdk.io/log v1.4.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/ChainSafe/go-schnorrkel v1.1.0 // indirect + github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/DataDog/zstd v1.5.0 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect @@ -105,11 +106,11 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.1 // indirect + github.com/cockroachdb/pebble v1.1.0 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/confio/ics23/go v0.9.0 // indirect @@ -122,8 +123,8 @@ require ( github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/creachadair/taskgroup v0.10.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/creachadair/taskgroup v0.4.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set v1.8.0 // indirect @@ -145,11 +146,11 @@ require ( github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect - github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/go-kit/kit v0.13.0 // indirect + github.com/getsentry/sentry-go v0.23.0 // indirect + github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect @@ -158,10 +159,10 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang/glog v1.2.2 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.3 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gopacket v1.1.19 // indirect @@ -169,7 +170,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect @@ -201,12 +202,12 @@ require ( 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 - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect @@ -226,12 +227,13 @@ 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/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect - github.com/minio/highwayhash v1.0.3 // indirect + github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -256,21 +258,21 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/otiai10/primes v0.0.0-20180210170552-f6d2a1ba97c4 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/tsdb v0.7.1 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rjeczalik/notify v0.9.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sasha-s/go-deadlock v0.3.5 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/sergi/go-diff v1.3.1 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/status-im/keycard-go v0.2.0 // indirect @@ -301,9 +303,9 @@ require ( go.nhat.io/matcher/v2 v2.0.0 // indirect go.nhat.io/wait v0.1.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/fx v1.19.2 // indirect @@ -311,16 +313,17 @@ require ( go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - gonum.org/v1/gonum v0.15.1 // indirect - google.golang.org/api v0.171.0 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + gonum.org/v1/gonum v0.13.0 // indirect + google.golang.org/api v0.155.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect lukechampine.com/blake3 v1.2.1 // indirect @@ -336,18 +339,14 @@ require ( ) require ( - github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/snksoft/crc v1.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect ) replace ( diff --git a/go.sum b/go.sum index abdc26e148..04150048bc 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5 cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -326,13 +326,13 @@ cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -630,9 +630,8 @@ cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+K cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -1059,8 +1058,8 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1316,9 +1315,8 @@ github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= -github.com/ChainSafe/go-schnorrkel v1.1.0 h1:rZ6EU+CZFCjB4sHUE1jIu8VDoB/wRKZxoe1tkcO71Wk= -github.com/ChainSafe/go-schnorrkel v1.1.0/go.mod h1:ABkENxiP+cvjFiByMIZ9LYbRoNNLeBLiakC1XeTFxfE= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= @@ -1417,9 +1415,8 @@ github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= -github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= -github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -1652,9 +1649,8 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4/go.mod h1:W8EnPSQ8Nv4fUjc/v1/8tHFqhuOJXnRub0dTfuAQktU= github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To= @@ -1702,6 +1698,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4= @@ -1716,17 +1714,15 @@ github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHy github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= -github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= -github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677/go.mod h1:890yq1fUb9b6dGNwssgeUO5vQV9qfXnCPxAJhBQfXw0= -github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= -github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= +github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= @@ -1743,11 +1739,11 @@ github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= github.com/cometbft/cometbft v0.34.27-alpha.1/go.mod h1:hct3hasQ2hIF3HoD7foVw4RaqTNSSeJ/lgcrVK6uDvs= github.com/cometbft/cometbft v0.37.4/go.mod h1:Cmg5Hp4sNpapm7j+x0xRyt2g0juQfmB752ous+pA0G8= -github.com/cometbft/cometbft v0.37.12 h1:gHrZAJPM+KOzCreP4y6KHOHu7HzHJT0fBG/aO7QvCzI= -github.com/cometbft/cometbft v0.37.12/go.mod h1:lvHwNAsn6R1tKnRhHvRMvL40efsZFbHpvEH/3KHoQI8= +github.com/cometbft/cometbft v0.37.5 h1:/U/TlgMh4NdnXNo+YU9T2NMCWyhXNDF34Mx582jlvq0= +github.com/cometbft/cometbft v0.37.5/go.mod h1:QC+mU0lBhKn8r9qvmnq53Dmf3DWBt4VtkcKw2C81wxY= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= -github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= -github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/cometbft/cometbft-db v0.12.0 h1:v77/z0VyfSU7k682IzZeZPFZrQAKiQwkqGN0QzAjMi0= +github.com/cometbft/cometbft-db v0.12.0/go.mod h1:aX2NbCrjNVd2ZajYxt1BsiFf/Z+TQ2MN0VxdicheYuw= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= @@ -1944,11 +1940,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= -github.com/creachadair/taskgroup v0.10.0 h1:xCz5Kk9PU3UL2VNF7XJ2MnIxIw48hUwYVLwtTzMzqtE= -github.com/creachadair/taskgroup v0.10.0/go.mod h1:9oDDPt/5QPS4iylvPMC81GRlj+1je8AFDbjUh4zaQWo= +github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= +github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -2122,6 +2118,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= @@ -2205,9 +2203,8 @@ github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TB github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= -github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -2251,9 +2248,8 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= -github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= -github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -2275,8 +2271,8 @@ github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= @@ -2402,8 +2398,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -2482,9 +2478,8 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= @@ -2607,9 +2602,8 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -2645,9 +2639,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= @@ -2765,8 +2758,6 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -3021,8 +3012,8 @@ github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -3060,7 +3051,6 @@ github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfR github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= @@ -3087,9 +3077,8 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -3219,6 +3208,7 @@ github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb44 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= @@ -3261,9 +3251,8 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcs github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= @@ -3374,7 +3363,6 @@ github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= @@ -3522,9 +3510,8 @@ github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04s github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -3587,17 +3574,16 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= -github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= @@ -3658,9 +3644,8 @@ github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -3669,9 +3654,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -3689,9 +3673,8 @@ github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -3708,9 +3691,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= @@ -3765,8 +3747,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= +github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -3807,9 +3789,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= -github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= -github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.13.0/go.mod h1:D2Wb7niIYmTB+gB8z7kh8tyP5ccof1dQ+SFk+WW5NtY= github.com/sashamelentyev/usestdlibvars v1.20.0/go.mod h1:0GaP+ecfZMXShS0A94CJn6aEuPRILv8h/VuWI9n1ygg= @@ -3833,8 +3814,8 @@ github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= @@ -3923,8 +3904,8 @@ github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -3944,8 +3925,8 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= @@ -4305,13 +4286,13 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.2 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0/go.mod h1:LsankqVDx4W+RhZNA5uWarULII/MBhF5qwCYxTuyXjs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.33.0/go.mod h1:y/SlJpJQPd2UzfBCj0E9Flk9FDCtTyqUmaCB41qFrWI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3/go.mod h1:Dts42MGkzZne2yCru741+bFiTMWkIj/LLRizad7b9tw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0/go.mod h1:vHItvsnJtp7ES++nFLLFBzUWny7fJQSvTlxFcqQGUr4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= @@ -4319,8 +4300,8 @@ go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdT go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= @@ -4336,15 +4317,15 @@ go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9deb go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= go.opentelemetry.io/otel/metric v0.32.3/go.mod h1:pgiGmKohxHyTPHGOff+vrtIH39/R9fiO/WoenUQ3kcc= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= @@ -4354,8 +4335,8 @@ go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwY go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= @@ -4479,8 +4460,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -4506,8 +4487,8 @@ golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZ golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -4683,8 +4664,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -4726,8 +4707,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -4750,8 +4731,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -4959,9 +4940,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -4985,8 +4965,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -5009,8 +4989,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -5183,9 +5163,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= @@ -5193,8 +5172,8 @@ gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= -gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -5282,8 +5261,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -5294,6 +5273,7 @@ google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -5484,8 +5464,8 @@ google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -5505,8 +5485,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go. google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -5531,8 +5511,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -5598,8 +5578,8 @@ google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSs google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -5625,8 +5605,8 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From df8b0e8712e2008993be3a40a5029599e6e8ff9d Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 29 Oct 2024 21:15:55 +0000 Subject: [PATCH 11/34] feat: whitelist connection gater (#3028) * bump go tss to remove dht * add whitelist fields * disable whitelist for localnet * bump go-tss * resolve whitelisted peers wip * dont disable whitelist in e2e tests * cleanup whitelist fields from config and fix e2e tests * bump go-tss * cleanup * bump go tss * use node accounts to get whitelisted peers * bump go tss * changelog * fix unit test * bump go tss * remove usage of pointers in node accounts * fix unit test * revert back to using keygen for whitelist peers --------- Co-authored-by: Alex Gartner --- changelog.md | 1 + cmd/zetaclientd/start.go | 15 ++++++++++++++- go.mod | 2 +- go.sum | 4 ++-- zetaclient/tss/tss_signer.go | 2 ++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 412141d2f8..9814a0662b 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,7 @@ * [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens * [3012](https://github.com/zeta-chain/node/pull/3012) - integrate authenticated calls erc20 smart contract functionality into protocol * [3025](https://github.com/zeta-chain/node/pull/3025) - standard memo for Bitcoin inbound +* [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater ### Refactor diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index f00abdcde1..67bd9830ee 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -13,10 +13,12 @@ import ( "time" "github.com/cometbft/cometbft/crypto/secp256k1" + "github.com/libp2p/go-libp2p/core/peer" maddr "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "gitlab.com/thorchain/tss/go-tss/conversion" "github.com/zeta-chain/node/pkg/authz" "github.com/zeta-chain/node/pkg/chains" @@ -204,8 +206,19 @@ func start(_ *cobra.Command, _ []string) error { } telemetryServer.SetIPAddress(cfg.PublicIP) + + keygen := appContext.GetKeygen() + whitelistedPeers := []peer.ID{} + for _, pk := range keygen.GranteePubkeys { + pid, err := conversion.Bech32PubkeyToPeerID(pk) + if err != nil { + return err + } + whitelistedPeers = append(whitelistedPeers, pid) + } + // Create TSS server - server, err := mc.SetupTSSServer(peers, priKey, preParams, appContext.Config(), tssKeyPass, true) + server, err := mc.SetupTSSServer(peers, priKey, preParams, appContext.Config(), tssKeyPass, true, whitelistedPeers) if err != nil { return fmt.Errorf("SetupTSSServer error: %w", err) } diff --git a/go.mod b/go.mod index 8ed2cb3548..8e769da887 100644 --- a/go.mod +++ b/go.mod @@ -367,5 +367,5 @@ replace ( github.com/bnb-chain/tss-lib => github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901 github.com/ethereum/go-ethereum => github.com/zeta-chain/go-ethereum v1.10.26-spc github.com/libp2p/go-libp2p => github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 - gitlab.com/thorchain/tss/go-tss => github.com/zeta-chain/go-tss v0.0.0-20240916173049-89fee4b0ae7f + gitlab.com/thorchain/tss/go-tss => github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 ) diff --git a/go.sum b/go.sum index 04150048bc..d2da2120e8 100644 --- a/go.sum +++ b/go.sum @@ -4204,8 +4204,8 @@ github.com/zeta-chain/go-ethereum v1.10.26-spc h1:NvY4rR9yw52wfxWt7YoFsWbaIwVMyO github.com/zeta-chain/go-ethereum v1.10.26-spc/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 h1:FmO3HfVdZ7LzxBUfg6sVzV7ilKElQU2DZm8PxJ7KcYI= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4/go.mod h1:TBv5NY/CqWYIfUstXO1fDWrt4bDoqgCw79yihqBspg8= -github.com/zeta-chain/go-tss v0.0.0-20240916173049-89fee4b0ae7f h1:XqUvw9a3EnDa271r5/tjRy90U2l1E8thdWzlrkbrEGE= -github.com/zeta-chain/go-tss v0.0.0-20240916173049-89fee4b0ae7f/go.mod h1:B1FDE6kHs8hozKSX1/iXgCdvlFbS6+FeAupoBHDK0Cc= +github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 h1:dBwx99+oymiyecnRGu1dnkJmYn2SAgBexBJ6nsdJt+E= +github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949/go.mod h1:B1FDE6kHs8hozKSX1/iXgCdvlFbS6+FeAupoBHDK0Cc= github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 h1:vck/FcIIpFOvpBUm0NO17jbEtmSz/W/a5Y4jRuSJl6I= github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138/go.mod h1:U494OsZTWsU75hqoriZgMdSsgSGP1mUL1jX+wN/Aez8= github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c h1:ZoFxMMZtivRLquXVq1sEVlT45UnTPMO1MSXtc88nDv4= diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index 0c7daa98e7..594784797c 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -148,6 +148,7 @@ func SetupTSSServer( cfg config.Config, tssPassword string, enableMonitor bool, + whitelistedPeers []gopeer.ID, ) (*tss.TssServer, error) { bootstrapPeers := peer log.Info().Msgf("Peers AddrList %v", bootstrapPeers) @@ -185,6 +186,7 @@ func SetupTSSServer( preParams, // use pre-generated pre-params if non-nil IP, // for docker test tssPassword, + whitelistedPeers, ) if err != nil { log.Error().Err(err).Msg("NewTSS error") From 60f0f1ae77a4a4a29e11bf8f749e0f6f96a2e5b1 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 30 Oct 2024 11:32:59 +0100 Subject: [PATCH 12/34] feat: distribute ZRC20 rewards function (#3019) * feat: distribute ZRC20 rewards function * add unit testing first batch * fix should return empty array if validators not set unit test * migrate to new test suite * remove function depending on staking precompile * commit last unit test changes * add e2e base * add distribution query client to e2e * e2e: finish disitribute tests * fix event name typo * Update precompiles/types/coin.go Co-authored-by: Tanmay * first batch of reviews * apply reviews * add nonZRC20 token e2e test * add fee collector balance check * check for err while DepositZRC20 * fix CI * fix gosec * modify CreateCoinSet to accept a common.Address * fix lint --------- Co-authored-by: Tanmay Co-authored-by: Tanmay --- changelog.md | 1 + cmd/zetacored/config/prefixes.go | 3 + cmd/zetae2e/local/local.go | 19 +- .../testdistribute/TestDistribute.abi | 64 + .../testdistribute/TestDistribute.bin | 1 + .../testdistribute/TestDistribute.go | 420 +++++ .../testdistribute/TestDistribute.json | 67 + .../testdistribute/TestDistribute.sol | 43 + e2e/contracts/testdistribute/bindings.go | 8 + e2e/contracts/teststaking/TestStaking.sol | 42 +- e2e/e2etests/e2etests.go | 35 +- .../test_precompiles_bank_through_contract.go | 20 +- e2e/e2etests/test_precompiles_distribute.go | 239 +++ ...precompiles_distribute_through_contract.go | 120 ++ e2e/runner/runner.go | 43 +- e2e/utils/require.go | 2 +- pkg/rpc/clients.go | 14 +- precompiles/bank/bank.go | 47 +- precompiles/bank/bank_test.go | 30 - precompiles/bank/logs.go | 4 +- precompiles/bank/method_balance_of.go | 16 +- precompiles/bank/method_deposit.go | 28 +- precompiles/bank/method_test.go | 12 +- precompiles/bank/method_withdraw.go | 32 +- precompiles/precompiles.go | 4 +- precompiles/prototype/prototype.go | 18 +- precompiles/staking/IStaking.abi | 49 + precompiles/staking/IStaking.gen.go | 177 +- precompiles/staking/IStaking.json | 49 + precompiles/staking/IStaking.sol | 33 +- precompiles/staking/const.go | 22 + precompiles/staking/logs.go | 42 +- precompiles/staking/method_distribute.go | 112 ++ precompiles/staking/method_distribute_test.go | 283 +++ .../staking/method_get_all_validators.go | 27 + .../staking/method_get_all_validators_test.go | 57 + precompiles/staking/method_get_shares.go | 50 + precompiles/staking/method_get_shares_test.go | 115 ++ precompiles/staking/method_move_stake.go | 85 + precompiles/staking/method_move_stake_test.go | 481 +++++ precompiles/staking/method_stake.go | 84 + precompiles/staking/method_stake_test.go | 316 ++++ precompiles/staking/method_unstake.go | 77 + precompiles/staking/method_unstake_test.go | 311 ++++ precompiles/staking/staking.go | 377 +--- precompiles/staking/staking_test.go | 1642 ++++------------- precompiles/types/address.go | 48 + precompiles/types/address_test.go | 68 + precompiles/{bank => types}/coin.go | 26 +- precompiles/{bank => types}/coin_test.go | 7 +- .../keeper/zrc20_cosmos_coins_mapping.go | 3 +- 51 files changed, 4070 insertions(+), 1803 deletions(-) create mode 100644 e2e/contracts/testdistribute/TestDistribute.abi create mode 100644 e2e/contracts/testdistribute/TestDistribute.bin create mode 100644 e2e/contracts/testdistribute/TestDistribute.go create mode 100644 e2e/contracts/testdistribute/TestDistribute.json create mode 100644 e2e/contracts/testdistribute/TestDistribute.sol create mode 100644 e2e/contracts/testdistribute/bindings.go create mode 100644 e2e/e2etests/test_precompiles_distribute.go create mode 100644 e2e/e2etests/test_precompiles_distribute_through_contract.go create mode 100644 precompiles/staking/const.go create mode 100644 precompiles/staking/method_distribute.go create mode 100644 precompiles/staking/method_distribute_test.go create mode 100644 precompiles/staking/method_get_all_validators.go create mode 100644 precompiles/staking/method_get_all_validators_test.go create mode 100644 precompiles/staking/method_get_shares.go create mode 100644 precompiles/staking/method_get_shares_test.go create mode 100644 precompiles/staking/method_move_stake.go create mode 100644 precompiles/staking/method_move_stake_test.go create mode 100644 precompiles/staking/method_stake.go create mode 100644 precompiles/staking/method_stake_test.go create mode 100644 precompiles/staking/method_unstake.go create mode 100644 precompiles/staking/method_unstake_test.go create mode 100644 precompiles/types/address.go create mode 100644 precompiles/types/address_test.go rename precompiles/{bank => types}/coin.go (59%) rename precompiles/{bank => types}/coin_test.go (81%) diff --git a/changelog.md b/changelog.md index 9814a0662b..1d5ceb9114 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ * [3012](https://github.com/zeta-chain/node/pull/3012) - integrate authenticated calls erc20 smart contract functionality into protocol * [3025](https://github.com/zeta-chain/node/pull/3025) - standard memo for Bitcoin inbound * [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater +* [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile ### Refactor diff --git a/cmd/zetacored/config/prefixes.go b/cmd/zetacored/config/prefixes.go index a96a4c57bc..5fcd9218a5 100644 --- a/cmd/zetacored/config/prefixes.go +++ b/cmd/zetacored/config/prefixes.go @@ -6,6 +6,9 @@ const ( // Bech32Prefix defines the Bech32 prefix used for Cronos Accounts Bech32Prefix = "zeta" + // ZRC20DenomPrefix defines the prefix for ZRC20 tokens when converted to sdk.Coin. + ZRC20DenomPrefix = "zrc20/" + // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address Bech32PrefixAccAddr = Bech32Prefix // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 4c6624451c..dca027f3c8 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -332,14 +332,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipPrecompiles { precompiledContractTests = []string{ - e2etests.TestPrecompilesPrototypeName, - e2etests.TestPrecompilesPrototypeThroughContractName, - e2etests.TestPrecompilesStakingName, - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - // e2etests.TestPrecompilesStakingThroughContractName, - e2etests.TestPrecompilesBankName, - e2etests.TestPrecompilesBankFailName, - e2etests.TestPrecompilesBankThroughContractName, + // e2etests.TestPrecompilesPrototypeName, + // e2etests.TestPrecompilesPrototypeThroughContractName, + // e2etests.TestPrecompilesStakingName, + // // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + // // e2etests.TestPrecompilesStakingThroughContractName, + // e2etests.TestPrecompilesBankName, + // e2etests.TestPrecompilesBankFailName, + // e2etests.TestPrecompilesBankThroughContractName, + e2etests.TestPrecompilesDistributeName, + e2etests.TestPrecompilesDistributeNonZRC20Name, + e2etests.TestPrecompilesDistributeThroughContractName, } } diff --git a/e2e/contracts/testdistribute/TestDistribute.abi b/e2e/contracts/testdistribute/TestDistribute.abi new file mode 100644 index 0000000000..26aa07af4e --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.abi @@ -0,0 +1,64 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distributeThroughContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/e2e/contracts/testdistribute/TestDistribute.bin b/e2e/contracts/testdistribute/TestDistribute.bin new file mode 100644 index 0000000000..5840bf96e5 --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.bin @@ -0,0 +1 @@ +60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033 diff --git a/e2e/contracts/testdistribute/TestDistribute.go b/e2e/contracts/testdistribute/TestDistribute.go new file mode 100644 index 0000000000..18b4201b1a --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.go @@ -0,0 +1,420 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package testdistribute + +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 +) + +// TestDistributeMetaData contains all meta data concerning the TestDistribute contract. +var TestDistributeMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distributeThroughContract\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033", +} + +// TestDistributeABI is the input ABI used to generate the binding from. +// Deprecated: Use TestDistributeMetaData.ABI instead. +var TestDistributeABI = TestDistributeMetaData.ABI + +// TestDistributeBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use TestDistributeMetaData.Bin instead. +var TestDistributeBin = TestDistributeMetaData.Bin + +// DeployTestDistribute deploys a new Ethereum contract, binding an instance of TestDistribute to it. +func DeployTestDistribute(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *TestDistribute, error) { + parsed, err := TestDistributeMetaData.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(TestDistributeBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &TestDistribute{TestDistributeCaller: TestDistributeCaller{contract: contract}, TestDistributeTransactor: TestDistributeTransactor{contract: contract}, TestDistributeFilterer: TestDistributeFilterer{contract: contract}}, nil +} + +// TestDistribute is an auto generated Go binding around an Ethereum contract. +type TestDistribute struct { + TestDistributeCaller // Read-only binding to the contract + TestDistributeTransactor // Write-only binding to the contract + TestDistributeFilterer // Log filterer for contract events +} + +// TestDistributeCaller is an auto generated read-only Go binding around an Ethereum contract. +type TestDistributeCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeTransactor is an auto generated write-only Go binding around an Ethereum contract. +type TestDistributeTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TestDistributeFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TestDistributeSession struct { + Contract *TestDistribute // 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 +} + +// TestDistributeCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TestDistributeCallerSession struct { + Contract *TestDistributeCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TestDistributeTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TestDistributeTransactorSession struct { + Contract *TestDistributeTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TestDistributeRaw is an auto generated low-level Go binding around an Ethereum contract. +type TestDistributeRaw struct { + Contract *TestDistribute // Generic contract binding to access the raw methods on +} + +// TestDistributeCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TestDistributeCallerRaw struct { + Contract *TestDistributeCaller // Generic read-only contract binding to access the raw methods on +} + +// TestDistributeTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TestDistributeTransactorRaw struct { + Contract *TestDistributeTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewTestDistribute creates a new instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistribute(address common.Address, backend bind.ContractBackend) (*TestDistribute, error) { + contract, err := bindTestDistribute(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &TestDistribute{TestDistributeCaller: TestDistributeCaller{contract: contract}, TestDistributeTransactor: TestDistributeTransactor{contract: contract}, TestDistributeFilterer: TestDistributeFilterer{contract: contract}}, nil +} + +// NewTestDistributeCaller creates a new read-only instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeCaller(address common.Address, caller bind.ContractCaller) (*TestDistributeCaller, error) { + contract, err := bindTestDistribute(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TestDistributeCaller{contract: contract}, nil +} + +// NewTestDistributeTransactor creates a new write-only instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeTransactor(address common.Address, transactor bind.ContractTransactor) (*TestDistributeTransactor, error) { + contract, err := bindTestDistribute(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TestDistributeTransactor{contract: contract}, nil +} + +// NewTestDistributeFilterer creates a new log filterer instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeFilterer(address common.Address, filterer bind.ContractFilterer) (*TestDistributeFilterer, error) { + contract, err := bindTestDistribute(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TestDistributeFilterer{contract: contract}, nil +} + +// bindTestDistribute binds a generic wrapper to an already deployed contract. +func bindTestDistribute(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TestDistributeMetaData.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 (_TestDistribute *TestDistributeRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDistribute.Contract.TestDistributeCaller.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 (_TestDistribute *TestDistributeRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.Contract.TestDistributeTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDistribute *TestDistributeRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDistribute.Contract.TestDistributeTransactor.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 (_TestDistribute *TestDistributeCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDistribute.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 (_TestDistribute *TestDistributeTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDistribute *TestDistributeTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDistribute.Contract.contract.Transact(opts, method, params...) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeTransactor) DistributeThroughContract(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.contract.Transact(opts, "distributeThroughContract", zrc20, amount) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeSession) DistributeThroughContract(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.Contract.DistributeThroughContract(&_TestDistribute.TransactOpts, zrc20, amount) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeTransactorSession) DistributeThroughContract(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.Contract.DistributeThroughContract(&_TestDistribute.TransactOpts, zrc20, amount) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _TestDistribute.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _TestDistribute.Contract.Fallback(&_TestDistribute.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _TestDistribute.Contract.Fallback(&_TestDistribute.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDistribute *TestDistributeTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.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 (_TestDistribute *TestDistributeSession) Receive() (*types.Transaction, error) { + return _TestDistribute.Contract.Receive(&_TestDistribute.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDistribute *TestDistributeTransactorSession) Receive() (*types.Transaction, error) { + return _TestDistribute.Contract.Receive(&_TestDistribute.TransactOpts) +} + +// TestDistributeDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the TestDistribute contract. +type TestDistributeDistributedIterator struct { + Event *TestDistributeDistributed // 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 *TestDistributeDistributedIterator) 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(TestDistributeDistributed) + 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(TestDistributeDistributed) + 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 *TestDistributeDistributedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TestDistributeDistributedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TestDistributeDistributed represents a Distributed event raised by the TestDistribute contract. +type TestDistributeDistributed struct { + Zrc20Distributor common.Address + Zrc20Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDistributed is a free log retrieval operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) FilterDistributed(opts *bind.FilterOpts, zrc20_distributor []common.Address, zrc20_token []common.Address) (*TestDistributeDistributedIterator, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _TestDistribute.contract.FilterLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return &TestDistributeDistributedIterator{contract: _TestDistribute.contract, event: "Distributed", logs: logs, sub: sub}, nil +} + +// WatchDistributed is a free log subscription operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) WatchDistributed(opts *bind.WatchOpts, sink chan<- *TestDistributeDistributed, zrc20_distributor []common.Address, zrc20_token []common.Address) (event.Subscription, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _TestDistribute.contract.WatchLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + 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(TestDistributeDistributed) + if err := _TestDistribute.contract.UnpackLog(event, "Distributed", 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 +} + +// ParseDistributed is a log parse operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) ParseDistributed(log types.Log) (*TestDistributeDistributed, error) { + event := new(TestDistributeDistributed) + if err := _TestDistribute.contract.UnpackLog(event, "Distributed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/e2e/contracts/testdistribute/TestDistribute.json b/e2e/contracts/testdistribute/TestDistribute.json new file mode 100644 index 0000000000..05ee369e1c --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.json @@ -0,0 +1,67 @@ +{ + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distributeThroughContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bin": "60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033" +} diff --git a/e2e/contracts/testdistribute/TestDistribute.sol b/e2e/contracts/testdistribute/TestDistribute.sol new file mode 100644 index 0000000000..5cf2277b88 --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +// @dev Interface to interact with distribute. +interface IDistribute { + function distribute( + address zrc20, + uint256 amount + ) external returns (bool success); +} + +// @dev Call IBank contract functions +contract TestDistribute { + event Distributed( + address indexed zrc20_distributor, + address indexed zrc20_token, + uint256 amount + ); + + IDistribute distr = IDistribute(0x0000000000000000000000000000000000000066); + + address immutable owner; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + function distributeThroughContract( + address zrc20, + uint256 amount + ) external onlyOwner returns (bool) { + return distr.distribute(zrc20, amount); + } + + fallback() external payable {} + + receive() external payable {} +} diff --git a/e2e/contracts/testdistribute/bindings.go b/e2e/contracts/testdistribute/bindings.go new file mode 100644 index 0000000000..765dfb5a8a --- /dev/null +++ b/e2e/contracts/testdistribute/bindings.go @@ -0,0 +1,8 @@ +//go:generate sh -c "solc TestDistribute.sol --combined-json abi,bin | jq '.contracts.\"TestDistribute.sol:TestDistribute\"' > TestDistribute.json" +//go:generate sh -c "cat TestDistribute.json | jq .abi > TestDistribute.abi" +//go:generate sh -c "cat TestDistribute.json | jq .bin | tr -d '\"' > TestDistribute.bin" +//go:generate sh -c "abigen --abi TestDistribute.abi --bin TestDistribute.bin --pkg testdistribute --type TestDistribute --out TestDistribute.go" + +package testdistribute + +var _ TestDistribute diff --git a/e2e/contracts/teststaking/TestStaking.sol b/e2e/contracts/teststaking/TestStaking.sol index 48a3837100..b6235ae658 100644 --- a/e2e/contracts/teststaking/TestStaking.sol +++ b/e2e/contracts/teststaking/TestStaking.sol @@ -36,13 +36,20 @@ interface IStaking { uint256 amount ) external returns (int64 completionTime); - function getAllValidators() external view returns (Validator[] calldata validators); + function getAllValidators() + external + view + returns (Validator[] calldata validators); - function getShares(address staker, string memory validator) external view returns (uint256 shares); + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares); } interface WZETA { function deposit() external payable; + function withdraw(uint256 wad) external; } @@ -94,18 +101,30 @@ contract TestStaking { wzeta.withdraw(wad); } - function stake(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stake( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { return staking.stake(staker, validator, amount); } - function stakeWithStateUpdate(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stakeWithStateUpdate( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { counter = counter + 1; bool success = staking.stake(staker, validator, amount); counter = counter + 1; return success; } - function stakeAndRevert(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stakeAndRevert( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { counter = counter + 1; staking.stake(staker, validator, amount); counter = counter + 1; @@ -129,15 +148,22 @@ contract TestStaking { return staking.moveStake(staker, validatorSrc, validatorDst, amount); } - function getShares(address staker, string memory validator) external view returns(uint256 shares) { + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares) { return staking.getShares(staker, validator); } - function getAllValidators() external view returns (Validator[] memory validators) { + function getAllValidators() + external + view + returns (Validator[] memory validators) + { return staking.getAllValidators(); } fallback() external payable {} receive() external payable {} -} \ No newline at end of file +} diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 98eac4397d..713ad935ff 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -172,13 +172,16 @@ const ( /* Stateful precompiled contracts tests */ - TestPrecompilesPrototypeName = "precompile_contracts_prototype" - TestPrecompilesPrototypeThroughContractName = "precompile_contracts_prototype_through_contract" - TestPrecompilesStakingName = "precompile_contracts_staking" - TestPrecompilesStakingThroughContractName = "precompile_contracts_staking_through_contract" - TestPrecompilesBankName = "precompile_contracts_bank" - TestPrecompilesBankFailName = "precompile_contracts_bank_fail" - TestPrecompilesBankThroughContractName = "precompile_contracts_bank_through_contract" + TestPrecompilesPrototypeName = "precompile_contracts_prototype" + TestPrecompilesPrototypeThroughContractName = "precompile_contracts_prototype_through_contract" + TestPrecompilesStakingName = "precompile_contracts_staking" + TestPrecompilesStakingThroughContractName = "precompile_contracts_staking_through_contract" + TestPrecompilesBankName = "precompile_contracts_bank" + TestPrecompilesBankFailName = "precompile_contracts_bank_fail" + TestPrecompilesBankThroughContractName = "precompile_contracts_bank_through_contract" + TestPrecompilesDistributeName = "precompile_contracts_distribute" + TestPrecompilesDistributeNonZRC20Name = "precompile_contracts_distribute_non_zrc20" + TestPrecompilesDistributeThroughContractName = "precompile_contracts_distribute_through_contract" ) // AllE2ETests is an ordered list of all e2e tests @@ -1041,4 +1044,22 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestPrecompilesBankThroughContract, ), + runner.NewE2ETest( + TestPrecompilesDistributeName, + "test stateful precompiled contracts distribute", + []runner.ArgDefinition{}, + TestPrecompilesDistribute, + ), + runner.NewE2ETest( + TestPrecompilesDistributeNonZRC20Name, + "test stateful precompiled contracts distribute with non ZRC20 tokens", + []runner.ArgDefinition{}, + TestPrecompilesDistributeNonZRC20, + ), + runner.NewE2ETest( + TestPrecompilesDistributeThroughContractName, + "test stateful precompiled contracts distribute through contract", + []runner.ArgDefinition{}, + TestPrecompilesDistributeThroughContract, + ), } diff --git a/e2e/e2etests/test_precompiles_bank_through_contract.go b/e2e/e2etests/test_precompiles_bank_through_contract.go index 6d6384fd9e..4baa6fefb5 100644 --- a/e2e/e2etests/test_precompiles_bank_through_contract.go +++ b/e2e/e2etests/test_precompiles_bank_through_contract.go @@ -58,7 +58,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { }() // Check initial balances. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -67,7 +67,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Deposit ERC20ZRC20 without allowance should fail") // Check balances, should be the same. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -80,7 +80,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -93,7 +93,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -102,7 +102,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequireTxSuccessful(r, receipt, "Depositting a correct amount should pass") // Balances should be transferred. Bank now locks 500 ZRC20 tokens. - balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) @@ -118,7 +118,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Withdrawing an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) @@ -127,7 +127,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequireTxSuccessful(r, receipt, "Withdraw correct amount should pass") // Balances should be reverted to initial state. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -156,7 +156,11 @@ func checkZRC20Balance(r *runner.E2ERunner, target common.Address) *big.Int { return bankZRC20Balance } -func checkCosmosBalance(r *runner.E2ERunner, bank *testbank.TestBank, zrc20, target common.Address) *big.Int { +func checkCosmosBalanceThroughBank( + r *runner.E2ERunner, + bank *testbank.TestBank, + zrc20, target common.Address, +) *big.Int { balance, err := bank.BalanceOf(&bind.CallOpts{Context: r.Ctx, From: r.ZEVMAuth.From}, zrc20, target) require.NoError(r, err) return balance diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go new file mode 100644 index 0000000000..36870e090c --- /dev/null +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -0,0 +1,239 @@ +package e2etests + +import ( + "math/big" + + "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/precompiles/bank" + "github.com/zeta-chain/node/precompiles/staking" + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0, "No arguments expected") + + var ( + spenderAddress = r.EVMAddress() + distributeContractAddress = staking.ContractAddress + lockerAddress = bank.ContractAddress + + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + + previousGasLimit = r.ZEVMAuth.GasLimit + ) + + // Set new gas limit to avoid out of gas errors. + r.ZEVMAuth.GasLimit = 10_000_000 + + // Set the test to reset the state after it finishes. + defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) + + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + require.NoError(r, err, "failed to create distribute contract caller") + + // DO NOT REMOVE - will be used in a subsequent PR when the ability to withdraw delegator rewards is introduced. + // Get validators through staking contract. + // validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + // require.NoError(r, err) + + // Check initial balances. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + tx, err := dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousand) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Allow 500. + approveAllowance(r, distributeContractAddress, fiveHundred) + + // Shouldn't be able to distribute more than allowed. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundredOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Raise the allowance to 1000. + approveAllowance(r, distributeContractAddress, oneThousand) + + // Shouldn't be able to distribute more than owned balance. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousandOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Should be able to distribute 500, which is within balance and allowance. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundred) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") + + balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) + require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) + require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + + // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. + r.WaitForBlocks(1) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // DO NOT REMOVE THE FOLLOWING CODE + // This section is commented until a following PR introduces the ability to withdraw delegator rewards. + // This validator checks will be used then to complete the whole e2e. + + // res, err := r.DistributionClient.ValidatorDistributionInfo( + // r.Ctx, + // &distributiontypes.QueryValidatorDistributionInfoRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }, + // ) + // require.NoError(r, err) + // fmt.Printf("Validator 0 distribution info: %+v\n", res) + + // res2, err := r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 0 outstanding rewards: %+v\n", res2) + + // res3, err := r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 0 commission: %+v\n", res3) + + // // Validator 1 + // res, err = r.DistributionClient.ValidatorDistributionInfo( + // r.Ctx, + // &distributiontypes.QueryValidatorDistributionInfoRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }, + // ) + // require.NoError(r, err) + // fmt.Printf("Validator 1 distribution info: %+v\n", res) + + // res2, err = r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 1 outstanding rewards: %+v\n", res2) + + // res3, err = r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 1 commission: %+v\n", res3) +} + +func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0, "No arguments expected") + + // Increase the gasLimit. It's required because of the gas consumed by precompiled functions. + previousGasLimit := r.ZEVMAuth.GasLimit + r.ZEVMAuth.GasLimit = 10_000_000 + defer func() { + r.ZEVMAuth.GasLimit = previousGasLimit + }() + + spender, dstrAddress := r.EVMAddress(), staking.ContractAddress + + // Create a staking contract caller. + dstrContract, err := staking.NewIStaking(dstrAddress, r.ZEVMClient) + require.NoError(r, err, "Failed to create staking contract caller") + + // Deposit and approve 50 WZETA for the test. + approveAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(50)) + r.DepositAndApproveWZeta(approveAmount) + + // Allow the staking contract to spend 25 WZeta tokens. + tx, err := r.WZeta.Approve(r.ZEVMAuth, dstrAddress, big.NewInt(25)) + require.NoError(r, err, "Error approving allowance for staking contract") + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.EqualValues(r, uint64(1), receipt.Status, "approve allowance tx failed") + + // Check the allowance of the staking in WZeta tokens. Should be 25. + allowance, err := r.WZeta.Allowance(&bind.CallOpts{Context: r.Ctx}, spender, dstrAddress) + require.NoError(r, err, "Error retrieving staking allowance") + require.EqualValues(r, uint64(25), allowance.Uint64(), "Error allowance for staking contract") + + // Call Distribute with 25 Non ZRC20 tokens. Should fail. + tx, err = dstrContract.Distribute(r.ZEVMAuth, r.WZetaAddr, big.NewInt(25)) + require.NoError(r, err, "Error calling staking.distribute()") + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.Equal(r, uint64(0), receipt.Status, "Non ZRC20 deposit should fail") +} + +// checkCosmosBalance checks the cosmos coin balance for an address. The coin is specified by its denom. +func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { + bal, err := r.BankClient.Balance( + r.Ctx, + &banktypes.QueryBalanceRequest{Address: address.String(), Denom: denom}, + ) + require.NoError(r, err) + + return bal.Balance.Amount.BigInt() +} + +func resetDistributionTest( + r *runner.E2ERunner, + lockerAddress common.Address, + previousGasLimit uint64, + amount *big.Int, +) { + r.ZEVMAuth.GasLimit = previousGasLimit + + // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. + tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, lockerAddress, big.NewInt(0)) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed") + + // Reset balance to 0 for spender; this is needed when running upgrade tests where this test runs twice. + tx, err = r.ERC20ZRC20.Transfer( + r.ZEVMAuth, + common.HexToAddress("0x000000000000000000000000000000000000dEaD"), + amount, + ) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "Resetting balance failed") +} diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go new file mode 100644 index 0000000000..444d8e6a59 --- /dev/null +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -0,0 +1,120 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/contracts/testdistribute" + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/precompiles/bank" + "github.com/zeta-chain/node/precompiles/staking" + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0, "No arguments expected") + + var ( + spenderAddress = r.EVMAddress() + distributeContractAddress = staking.ContractAddress + lockerAddress = bank.ContractAddress + + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + + previousGasLimit = r.ZEVMAuth.GasLimit + ) + + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + require.NoError(r, err, "failed to create distribute contract caller") + + _, tx, testDstrContract, err := testdistribute.DeployTestDistribute(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "deployment of disitributor caller contract failed") + + // Set new gas limit to avoid out of gas errors. + r.ZEVMAuth.GasLimit = 10_000_000 + + // Set the test to reset the state after it finishes. + defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) + + // Check initial balances. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) + utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Allow 500. + approveAllowance(r, distributeContractAddress, fiveHundred) + + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundredOne) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Raise the allowance to 1000. + approveAllowance(r, distributeContractAddress, oneThousand) + + // Shouldn't be able to distribute more than owned balance. + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousandOne) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + // Should be able to distribute 500, which is within balance and allowance. + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundred) + utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") + + balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 1000, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + + eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) + require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) + require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + + // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. + r.WaitForBlocks(1) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) +} + +func distributeThroughContract( + r *runner.E2ERunner, + dstr *testdistribute.TestDistribute, + zrc20Address common.Address, + amount *big.Int, +) *types.Receipt { + tx, err := dstr.DistributeThroughContract(r.ZEVMAuth, zrc20Address, amount) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + return receipt +} diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 03cafb6fc4..cf7a8e6f02 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -9,8 +9,10 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" + "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" @@ -74,6 +76,7 @@ type E2ERunner struct { SolanaDeployerAddress solana.PublicKey TONDeployer *tonrunner.Deployer TONGateway *toncontracts.Gateway + FeeCollectorAddress types.AccAddress // all clients. // a reference to this type is required to enable creating a new E2ERunner. @@ -86,14 +89,15 @@ type E2ERunner struct { SolanaClient *rpc.Client // zetacored grpc clients - AuthorityClient authoritytypes.QueryClient - CctxClient crosschaintypes.QueryClient - FungibleClient fungibletypes.QueryClient - AuthClient authtypes.QueryClient - BankClient banktypes.QueryClient - StakingClient stakingtypes.QueryClient - ObserverClient observertypes.QueryClient - LightclientClient lightclienttypes.QueryClient + AuthorityClient authoritytypes.QueryClient + CctxClient crosschaintypes.QueryClient + FungibleClient fungibletypes.QueryClient + AuthClient authtypes.QueryClient + BankClient banktypes.QueryClient + StakingClient stakingtypes.QueryClient + ObserverClient observertypes.QueryClient + LightclientClient lightclienttypes.QueryClient + DistributionClient distributiontypes.QueryClient // optional zeta (cosmos) client // typically only in test runners that need it @@ -187,18 +191,21 @@ func NewE2ERunner( Account: account, + FeeCollectorAddress: authtypes.NewModuleAddress(authtypes.FeeCollectorName), + Clients: clients, - ZEVMClient: clients.Zevm, - EVMClient: clients.Evm, - AuthorityClient: clients.Zetacore.Authority, - CctxClient: clients.Zetacore.Crosschain, - FungibleClient: clients.Zetacore.Fungible, - AuthClient: clients.Zetacore.Auth, - BankClient: clients.Zetacore.Bank, - StakingClient: clients.Zetacore.Staking, - ObserverClient: clients.Zetacore.Observer, - LightclientClient: clients.Zetacore.Lightclient, + ZEVMClient: clients.Zevm, + EVMClient: clients.Evm, + AuthorityClient: clients.Zetacore.Authority, + CctxClient: clients.Zetacore.Crosschain, + FungibleClient: clients.Zetacore.Fungible, + AuthClient: clients.Zetacore.Auth, + BankClient: clients.Zetacore.Bank, + StakingClient: clients.Zetacore.Staking, + ObserverClient: clients.Zetacore.Observer, + LightclientClient: clients.Zetacore.Lightclient, + DistributionClient: clients.Zetacore.Distribution, EVMAuth: clients.EvmAuth, ZEVMAuth: clients.ZevmAuth, diff --git a/e2e/utils/require.go b/e2e/utils/require.go index 3dedb7dd26..8bf9c5f5d0 100644 --- a/e2e/utils/require.go +++ b/e2e/utils/require.go @@ -38,7 +38,7 @@ func RequireTxSuccessful(t require.TestingT, receipt *ethtypes.Receipt, msgAndAr // RequiredTxFailed checks if the receipt status is failed. // Currently, it accepts eth receipt, but we can make this more generic by using type assertion. func RequiredTxFailed(t require.TestingT, receipt *ethtypes.Receipt, msgAndArgs ...any) { - msg := "receipt status is not successful: %s" + msg := "receipt status is not failed: %s" require.Equal( t, ethtypes.ReceiptStatusFailed, diff --git a/pkg/rpc/clients.go b/pkg/rpc/clients.go index 26ecbe729f..1dc0d7314e 100644 --- a/pkg/rpc/clients.go +++ b/pkg/rpc/clients.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" feemarkettypes "github.com/zeta-chain/ethermint/x/feemarket/types" @@ -35,6 +36,8 @@ type Clients struct { Staking stakingtypes.QueryClient // Upgrade is a github.com/cosmos/cosmos-sdk/x/upgrade/types QueryClient Upgrade upgradetypes.QueryClient + // Distribution is a "github.com/cosmos/cosmos-sdk/x/distribution/types" QueryClient + Distribution distributiontypes.QueryClient // ZetaCore specific clients @@ -65,11 +68,12 @@ type Clients struct { func newClients(ctx client.Context) (Clients, error) { return Clients{ // Cosmos SDK clients - Auth: authtypes.NewQueryClient(ctx), - Bank: banktypes.NewQueryClient(ctx), - Staking: stakingtypes.NewQueryClient(ctx), - Upgrade: upgradetypes.NewQueryClient(ctx), - Authority: authoritytypes.NewQueryClient(ctx), + Auth: authtypes.NewQueryClient(ctx), + Bank: banktypes.NewQueryClient(ctx), + Staking: stakingtypes.NewQueryClient(ctx), + Upgrade: upgradetypes.NewQueryClient(ctx), + Authority: authoritytypes.NewQueryClient(ctx), + Distribution: distributiontypes.NewQueryClient(ctx), // ZetaCore specific clients Crosschain: crosschaintypes.NewQueryClient(ctx), Fungible: fungibletypes.NewQueryClient(ctx), diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 51a559edd3..b92b4a8217 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) @@ -49,7 +49,7 @@ func initABI() { } type Contract struct { - ptypes.BaseContract + precompiletypes.BaseContract bankKeeper bank.Keeper fungibleKeeper fungiblekeeper.Keeper @@ -74,11 +74,12 @@ func NewIBankContract( // This avoids instantiating it every time deposit or withdraw are called. zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { + ctx.Logger().Error("bank contract failed to get ZRC20 ABI", "error", err) return nil } return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), + BaseContract: precompiletypes.NewBaseContract(ContractAddress), bankKeeper: bankKeeper, fungibleKeeper: fungibleKeeper, zrc20ABI: zrc20ABI, @@ -130,13 +131,13 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) switch method.Name { // Deposit and Withdraw methods are both not allowed in read-only mode. case DepositMethodName, WithdrawMethodName: if readOnly { - return nil, ptypes.ErrWriteMethod{ + return nil, precompiletypes.ErrWriteMethod{ Method: method.Name, } } @@ -175,42 +176,8 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } } - -// getEVMCallerAddress returns the caller address. -// Usually the caller is the contract.CallerAddress, which is the address of the contract that called the precompiled contract. -// If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, -// on which case there is a need to set the caller to the evm.Origin. -func getEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, error) { - caller := contract.CallerAddress - if contract.CallerAddress != evm.Origin { - caller = evm.Origin - } - - return caller, nil -} - -// getCosmosAddress returns the counterpart cosmos address of the given ethereum address. -// It checks if the address is empty or blocked by the bank keeper. -func getCosmosAddress(bankKeeper bank.Keeper, addr common.Address) (sdk.AccAddress, error) { - toAddr := sdk.AccAddress(addr.Bytes()) - if toAddr.Empty() { - return nil, &ptypes.ErrInvalidAddr{ - Got: toAddr.String(), - Reason: "empty address", - } - } - - if bankKeeper.BlockedAddr(toAddr) { - return nil, &ptypes.ErrInvalidAddr{ - Got: toAddr.String(), - Reason: "destination address blocked by bank keeper", - } - } - - return toAddr, nil -} diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index 518c330a7f..a42bdeb88a 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -2,16 +2,12 @@ package bank import ( "encoding/json" - "math/big" "testing" storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/require" ethermint "github.com/zeta-chain/ethermint/types" "github.com/zeta-chain/node/testutil/keeper" - "github.com/zeta-chain/node/testutil/sample" ) func Test_IBankContract(t *testing.T) { @@ -128,29 +124,3 @@ func Test_InvalidABI(t *testing.T) { initABI() } - -func Test_getEVMCallerAddress(t *testing.T) { - mockEVM := vm.EVM{ - TxContext: vm.TxContext{ - Origin: common.Address{}, - }, - } - - mockVMContract := vm.NewContract( - contractRef{address: common.Address{}}, - contractRef{address: ContractAddress}, - big.NewInt(0), - 0, - ) - - // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. - caller, err := getEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, common.Address{}, caller, "address shouldn be the same") - - // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. - mockEVM.Origin = sample.EthAddress() - caller, err = getEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") -} diff --git a/precompiles/bank/logs.go b/precompiles/bank/logs.go index 36b877dfa5..9043cc63f9 100644 --- a/precompiles/bank/logs.go +++ b/precompiles/bank/logs.go @@ -28,8 +28,8 @@ func (c *Contract) addEventLog( topics, err := logs.MakeTopics( event, - []interface{}{common.BytesToAddress(eventData.zrc20Addr.Bytes())}, - []interface{}{common.BytesToAddress(eventData.zrc20Token.Bytes())}, + []interface{}{eventData.zrc20Addr}, + []interface{}{eventData.zrc20Token}, []interface{}{eventData.cosmosCoin}, ) if err != nil { diff --git a/precompiles/bank/method_balance_of.go b/precompiles/bank/method_balance_of.go index e4bc644a2b..85765d2ab8 100644 --- a/precompiles/bank/method_balance_of.go +++ b/precompiles/bank/method_balance_of.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) // balanceOf returns the balance of cosmos coins minted by the bank's deposit function, @@ -19,7 +19,7 @@ func (c *Contract) balanceOf( args []interface{}, ) (result []byte, err error) { if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -32,7 +32,7 @@ func (c *Contract) balanceOf( } // Get the counterpart cosmos address. - toAddr, err := getCosmosAddress(c.bankKeeper, addr) + toAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, addr) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (c *Contract) balanceOf( // Do not check for t.Paused, as the balance is read only the EOA won't be able to operate. _, found := c.fungibleKeeper.GetForeignCoins(ctx, zrc20Addr.String()) if !found { - return nil, &ptypes.ErrInvalidToken{ + return nil, &precompiletypes.ErrInvalidToken{ Got: zrc20Addr.String(), Reason: "token is not a whitelisted ZRC20", } @@ -49,9 +49,9 @@ func (c *Contract) balanceOf( // Bank Keeper GetBalance returns the specified Cosmos coin balance for a given address. // Check explicitly the balance is a non-negative non-nil value. - coin := c.bankKeeper.GetBalance(ctx, toAddr, ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, toAddr, precompiletypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &precompiletypes.ErrInvalidCoin{ Got: coin.GetDenom(), Negative: coin.IsNegative(), Nil: coin.IsNil(), @@ -64,14 +64,14 @@ func (c *Contract) balanceOf( func unpackBalanceOfArgs(args []interface{}) (zrc20Addr common.Address, addr common.Address, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, common.Address{}, &ptypes.ErrInvalidAddr{ + return common.Address{}, common.Address{}, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } addr, ok = args[1].(common.Address) if !ok { - return common.Address{}, common.Address{}, &ptypes.ErrInvalidAddr{ + return common.Address{}, common.Address{}, &precompiletypes.ErrInvalidAddr{ Got: addr.String(), } } diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 1d5792d8a3..76c21d926a 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/x/fungible/types" ) @@ -34,7 +34,7 @@ func (c *Contract) deposit( // This function is developed using the Check - Effects - Interactions pattern: // 1. Check everything is correct. if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -48,13 +48,13 @@ func (c *Contract) deposit( } // Get the correct caller address. - caller, err := getEVMCallerAddress(evm, contract) + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. - toAddr, err := getCosmosAddress(c.bankKeeper, caller) + toAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } @@ -70,7 +70,7 @@ func (c *Contract) deposit( []interface{}{caller}, ) if err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "balanceOf", Got: err.Error(), } @@ -78,13 +78,13 @@ func (c *Contract) deposit( balance, ok := resBalanceOf[0].(*big.Int) if !ok { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ Got: "ZRC20 balanceOf returned an unexpected type", } } if balance.Cmp(amount) < 0 || balance.Cmp(big.NewInt(0)) <= 0 { - return nil, &ptypes.ErrInvalidAmount{ + return nil, &precompiletypes.ErrInvalidAmount{ Got: balance.String(), } } @@ -94,14 +94,14 @@ func (c *Contract) deposit( // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". // - Mint coins to the fungible module. // - Send coins from fungible to the caller. - coinSet, err := createCoinSet(ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) if err != nil { return nil, err } // 2. Effect: subtract balance. if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), } @@ -109,7 +109,7 @@ func (c *Contract) deposit( // 3. Interactions: create cosmos coin and send. if err := c.bankKeeper.MintCoins(ctx, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "MintCoins", Got: err.Error(), } @@ -117,14 +117,14 @@ func (c *Contract) deposit( err = c.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, coinSet) if err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "SendCoinsFromModuleToAccount", Got: err.Error(), } } if err := c.addEventLog(ctx, evm.StateDB, DepositEventName, eventData{caller, zrc20Addr, toAddr.String(), coinSet.Denoms()[0], amount}); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "AddDepositLog", Got: err.Error(), } @@ -136,14 +136,14 @@ func (c *Contract) deposit( func unpackDepositArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } amount, ok = args[1].(*big.Int) if !ok || amount == nil || amount.Sign() <= 0 { - return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ Got: amount.String(), } } diff --git a/precompiles/bank/method_test.go b/precompiles/bank/method_test.go index e416187490..38fc715af6 100644 --- a/precompiles/bank/method_test.go +++ b/precompiles/bank/method_test.go @@ -16,7 +16,7 @@ import ( "github.com/zeta-chain/ethermint/x/evm/statedb" "github.com/zeta-chain/node/pkg/chains" erc1967proxy "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/keeper" "github.com/zeta-chain/node/testutil/sample" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" @@ -46,7 +46,7 @@ func Test_Methods(t *testing.T) { success, err := ts.bankContract.Run(ts.mockEVM, ts.mockVMContract, true) require.ErrorIs( t, - ptypes.ErrWriteMethod{ + precompiletypes.ErrWriteMethod{ Method: "deposit", }, err) @@ -73,7 +73,7 @@ func Test_Methods(t *testing.T) { success, err := ts.bankContract.Run(ts.mockEVM, ts.mockVMContract, true) require.ErrorIs( t, - ptypes.ErrWriteMethod{ + precompiletypes.ErrWriteMethod{ Method: "withdraw", }, err) @@ -101,7 +101,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInvalidAmount{ + precompiletypes.ErrInvalidAmount{ Got: "0", }, err, @@ -248,7 +248,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInvalidAmount{ + precompiletypes.ErrInvalidAmount{ Got: "1000", }, err, @@ -459,7 +459,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInsufficientBalance{ + precompiletypes.ErrInsufficientBalance{ Requested: "501", Got: "500", }, diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index e071ed04cd..a1308146c1 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/x/fungible/types" ) @@ -29,7 +29,7 @@ func (c *Contract) withdraw( ) (result []byte, err error) { // 1. Check everything is correct. if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -43,50 +43,50 @@ func (c *Contract) withdraw( } // Get the correct caller address. - caller, err := getEVMCallerAddress(evm, contract) + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. // This address should have enough cosmos coin balance as the requested amount. - fromAddr, err := getCosmosAddress(c.bankKeeper, caller) + fromAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } // Safety check: token has to be a non-paused whitelisted ZRC20. if err := c.fungibleKeeper.IsValidZRC20(ctx, zrc20Addr); err != nil { - return nil, &ptypes.ErrInvalidToken{ + return nil, &precompiletypes.ErrInvalidToken{ Got: zrc20Addr.String(), Reason: err.Error(), } } // Caller has to have enough cosmos coin balance to withdraw the requested amount. - coin := c.bankKeeper.GetBalance(ctx, fromAddr, ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, fromAddr, precompiletypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: "invalid coin", } } if coin.Amount.LT(math.NewIntFromBigInt(amount)) { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: coin.Amount.String(), } } - coinSet, err := createCoinSet(ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) if err != nil { return nil, err } // Check if bank address has enough ZRC20 balance. if err := c.fungibleKeeper.CheckZRC20Balance(ctx, c.zrc20ABI, zrc20Addr, c.Address(), amount); err != nil { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: err.Error(), } @@ -94,14 +94,14 @@ func (c *Contract) withdraw( // 2. Effect: burn cosmos coin balance. if err := c.bankKeeper.SendCoinsFromAccountToModule(ctx, fromAddr, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "SendCoinsFromAccountToModule", Got: err.Error(), } } if err := c.bankKeeper.BurnCoins(ctx, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "BurnCoins", Got: err.Error(), } @@ -109,14 +109,14 @@ func (c *Contract) withdraw( // 3. Interactions: send ZRC20. if err := c.fungibleKeeper.UnlockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, c.Address(), amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "UnlockZRC20InBank", Got: err.Error(), } } if err := c.addEventLog(ctx, evm.StateDB, WithdrawEventName, eventData{caller, zrc20Addr, fromAddr.String(), coinSet.Denoms()[0], amount}); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "AddWithdrawLog", Got: err.Error(), } @@ -128,14 +128,14 @@ func (c *Contract) withdraw( func unpackWithdrawArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } amount, ok = args[1].(*big.Int) if !ok || amount == nil || amount.Sign() <= 0 { - return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ Got: amount.String(), } } diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index cdd5e2ff74..b9d167dbac 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -49,8 +49,8 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { - stakingContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { - return staking.NewIStakingContract(stakingKeeper, cdc, gasConfig) + stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + return staking.NewIStakingContract(ctx, stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) } // Append the staking contract to the precompiledContracts slice. diff --git a/precompiles/prototype/prototype.go b/precompiles/prototype/prototype.go index 123885ccb4..31f1bcaaec 100644 --- a/precompiles/prototype/prototype.go +++ b/precompiles/prototype/prototype.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) @@ -55,7 +55,7 @@ func initABI() { } type Contract struct { - ptypes.BaseContract + precompiletypes.BaseContract fungibleKeeper fungiblekeeper.Keeper cdc codec.Codec @@ -68,7 +68,7 @@ func NewIPrototypeContract( kvGasConfig storetypes.GasConfig, ) *Contract { return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), + BaseContract: precompiletypes.NewBaseContract(ContractAddress), fungibleKeeper: *fungibleKeeper, cdc: cdc, kvGasConfig: kvGasConfig, @@ -106,7 +106,7 @@ func (c *Contract) RequiredGas(input []byte) uint64 { // Bech32ToHexAddr converts a bech32 address to a hex address. func (c *Contract) Bech32ToHexAddr(method *abi.Method, args []interface{}) ([]byte, error) { if len(args) != 1 { - return nil, &ptypes.ErrInvalidNumberOfArgs{ + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 1, } @@ -144,7 +144,7 @@ func (c *Contract) Bech32ToHexAddr(method *abi.Method, args []interface{}) ([]by // Bech32ify converts a hex address to a bech32 address. func (c *Contract) Bech32ify(method *abi.Method, args []interface{}) ([]byte, error) { if len(args) != 2 { - return nil, &ptypes.ErrInvalidNumberOfArgs{ + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, } @@ -198,7 +198,7 @@ func (c *Contract) GetGasStabilityPoolBalance( args []interface{}, ) ([]byte, error) { if len(args) != 1 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 1, }) @@ -207,7 +207,7 @@ func (c *Contract) GetGasStabilityPoolBalance( // Unwrap arguments. The chainID is the first and unique argument. chainID, ok := args[0].(int64) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: chainID, } } @@ -233,7 +233,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, _ bool) ([]byte, erro return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) switch method.Name { case GetGasStabilityPoolBalanceName: @@ -252,7 +252,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, _ bool) ([]byte, erro case Bech32ifyMethodName: return c.Bech32ify(method, args) default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } diff --git a/precompiles/staking/IStaking.abi b/precompiles/staking/IStaking.abi index fceba4d682..da1a9e6ffc 100644 --- a/precompiles/staking/IStaking.abi +++ b/precompiles/staking/IStaking.abi @@ -1,4 +1,29 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -80,6 +105,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distribute", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "getAllValidators", diff --git a/precompiles/staking/IStaking.gen.go b/precompiles/staking/IStaking.gen.go index 01226d4ecf..d4f7495d37 100644 --- a/precompiles/staking/IStaking.gen.go +++ b/precompiles/staking/IStaking.gen.go @@ -39,7 +39,7 @@ type Validator struct { // IStakingMetaData contains all meta data concerning the IStaking contract. var IStakingMetaData = &bind.MetaData{ - ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // IStakingABI is the input ABI used to generate the binding from. @@ -250,6 +250,27 @@ func (_IStaking *IStakingCallerSession) GetShares(staker common.Address, validat return _IStaking.Contract.GetShares(&_IStaking.CallOpts, staker, validator) } +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingTransactor) Distribute(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.contract.Transact(opts, "distribute", zrc20, amount) +} + +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingSession) Distribute(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.Contract.Distribute(&_IStaking.TransactOpts, zrc20, amount) +} + +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingTransactorSession) Distribute(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.Contract.Distribute(&_IStaking.TransactOpts, zrc20, amount) +} + // MoveStake is a paid mutator transaction binding the contract method 0xd11a93d0. // // Solidity: function moveStake(address staker, string validatorSrc, string validatorDst, uint256 amount) returns(int64 completionTime) @@ -313,6 +334,160 @@ func (_IStaking *IStakingTransactorSession) Unstake(staker common.Address, valid return _IStaking.Contract.Unstake(&_IStaking.TransactOpts, staker, validator, amount) } +// IStakingDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the IStaking contract. +type IStakingDistributedIterator struct { + Event *IStakingDistributed // 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 *IStakingDistributedIterator) 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(IStakingDistributed) + 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(IStakingDistributed) + 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 *IStakingDistributedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IStakingDistributedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IStakingDistributed represents a Distributed event raised by the IStaking contract. +type IStakingDistributed struct { + Zrc20Distributor common.Address + Zrc20Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDistributed is a free log retrieval operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) FilterDistributed(opts *bind.FilterOpts, zrc20_distributor []common.Address, zrc20_token []common.Address) (*IStakingDistributedIterator, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.FilterLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return &IStakingDistributedIterator{contract: _IStaking.contract, event: "Distributed", logs: logs, sub: sub}, nil +} + +// WatchDistributed is a free log subscription operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) WatchDistributed(opts *bind.WatchOpts, sink chan<- *IStakingDistributed, zrc20_distributor []common.Address, zrc20_token []common.Address) (event.Subscription, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.WatchLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + 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(IStakingDistributed) + if err := _IStaking.contract.UnpackLog(event, "Distributed", 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 +} + +// ParseDistributed is a log parse operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) ParseDistributed(log types.Log) (*IStakingDistributed, error) { + event := new(IStakingDistributed) + if err := _IStaking.contract.UnpackLog(event, "Distributed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // IStakingMoveStakeIterator is returned from FilterMoveStake and is used to iterate over the raw logs and unpacked data for MoveStake events raised by the IStaking contract. type IStakingMoveStakeIterator struct { Event *IStakingMoveStake // Event containing the contract specifics and raw log diff --git a/precompiles/staking/IStaking.json b/precompiles/staking/IStaking.json index cff6a7a365..d4e0bb75f0 100644 --- a/precompiles/staking/IStaking.json +++ b/precompiles/staking/IStaking.json @@ -1,5 +1,30 @@ { "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -81,6 +106,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distribute", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "getAllValidators", diff --git a/precompiles/staking/IStaking.sol b/precompiles/staking/IStaking.sol index c6b2642a4c..dece711d71 100644 --- a/precompiles/staking/IStaking.sol +++ b/precompiles/staking/IStaking.sol @@ -5,9 +5,7 @@ pragma solidity ^0.8.26; address constant ISTAKING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000066; // 102 /// @dev The IStaking contract's instance. -IStaking constant ISTAKING_CONTRACT = IStaking( - ISTAKING_PRECOMPILE_ADDRESS -); +IStaking constant ISTAKING_CONTRACT = IStaking(ISTAKING_PRECOMPILE_ADDRESS); /// @notice Bond status for validator enum BondStatus { @@ -58,6 +56,16 @@ interface IStaking { uint256 amount ); + /// @notice Distributed event is emitted when distribute function is called successfully. + /// @param zrc20_distributor Distributor address. + /// @param zrc20_token ZRC20 token address. + /// @param amount Distributed amount. + event Distributed( + address indexed zrc20_distributor, + address indexed zrc20_token, + uint256 amount + ); + /// @notice Stake coins to validator /// @param staker Staker address /// @param validator Validator address @@ -95,9 +103,24 @@ interface IStaking { /// @notice Get all validators /// @return validators All validators - function getAllValidators() external view returns (Validator[] calldata validators); + function getAllValidators() + external + view + returns (Validator[] calldata validators); /// @notice Get shares for staker in validator /// @return shares Staker shares in validator - function getShares(address staker, string memory validator) external view returns (uint256 shares); + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares); + + /// @notice Distribute a ZRC20 token as staking rewards. + /// @param zrc20 The ZRC20 token address to be distributed. + /// @param amount The amount of ZRC20 tokens to distribute. + /// @return success Boolean indicating whether the distribution was successful. + function distribute( + address zrc20, + uint256 amount + ) external returns (bool success); } diff --git a/precompiles/staking/const.go b/precompiles/staking/const.go new file mode 100644 index 0000000000..8500e723f4 --- /dev/null +++ b/precompiles/staking/const.go @@ -0,0 +1,22 @@ +package staking + +const ( + DistributeMethodName = "distribute" + DistributeEventName = "Distributed" + DistributeMethodGas = 10000 + + GetAllValidatorsMethodName = "getAllValidators" + GetSharesMethodName = "getShares" + + MoveStakeMethodName = "moveStake" + MoveStakeEventName = "MoveStake" + MoveStakeMethodGas = 10000 + + StakeMethodName = "stake" + StakeEventName = "Stake" + StakeMethodGas = 10000 + + UnstakeMethodName = "unstake" + UnstakeEventName = "Unstake" + UnstakeMethodGas = 1000 +) diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index ea8e51274c..c8d1db24e2 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -10,13 +10,7 @@ import ( "github.com/zeta-chain/node/precompiles/logs" ) -const ( - StakeEventName = "Stake" - UnstakeEventName = "Unstake" - MoveStakeEventName = "MoveStake" -) - -func (c *Contract) AddStakeLog( +func (c *Contract) addStakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -49,7 +43,7 @@ func (c *Contract) AddStakeLog( return nil } -func (c *Contract) AddUnstakeLog( +func (c *Contract) addUnstakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -81,7 +75,7 @@ func (c *Contract) AddUnstakeLog( return nil } -func (c *Contract) AddMoveStakeLog( +func (c *Contract) addMoveStakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -123,3 +117,33 @@ func (c *Contract) AddMoveStakeLog( return nil } + +func (c *Contract) addDistributeLog( + ctx sdk.Context, + stateDB vm.StateDB, + distributor common.Address, + zrc20Token common.Address, + amount *big.Int, +) error { + event := c.Abi().Events[DistributeEventName] + + topics, err := logs.MakeTopics( + event, + []interface{}{distributor}, + []interface{}{zrc20Token}, + ) + if err != nil { + return err + } + + data, err := logs.PackArguments([]logs.Argument{ + {Type: "uint256", Value: amount}, + }) + if err != nil { + return err + } + + logs.AddLog(ctx, c.Address(), stateDB, topics, data) + + return nil +} diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go new file mode 100644 index 0000000000..1fa7351505 --- /dev/null +++ b/precompiles/staking/method_distribute.go @@ -0,0 +1,112 @@ +package staking + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + "github.com/zeta-chain/node/precompiles/bank" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +// function distribute(address zrc20, uint256 amount) external returns (bool success) +func (c *Contract) distribute( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + } + } + + // Unpack arguments and check if they are valid. + zrc20Addr, amount, err := unpackDistributeArgs(args) + if err != nil { + return nil, err + } + + // Get the original caller address. Necessary for LockZRC20 to work. + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) + if err != nil { + return nil, err + } + + // Create the coinSet in advance, if this step fails do not lock ZRC20. + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) + if err != nil { + return nil, err + } + + // LockZRC20 locks the ZRC20 under the locker address. + // It performs all the necessary checks such as allowance in order to execute a transferFrom. + // - spender is the staking contract address (c.Address()). + // - owner is the caller address. + // - locker is the bank address. Assets are locked under this address to prevent liquidity fragmentation. + if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, bank.ContractAddress, amount); err != nil { + return nil, &precompiletypes.ErrUnexpected{ + When: "LockZRC20InBank", + Got: err.Error(), + } + } + + // With the ZRC20 locked, proceed to mint the cosmos coins. + if err := c.bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { + return nil, &precompiletypes.ErrUnexpected{ + When: "MintCoins", + Got: err.Error(), + } + } + + // Send the coins to the FeePool. + if err := c.bankKeeper.SendCoinsFromModuleToModule(ctx, fungibletypes.ModuleName, authtypes.FeeCollectorName, coinSet); err != nil { + return nil, &precompiletypes.ErrUnexpected{ + When: "SendCoinsFromModuleToModule", + Got: err.Error(), + } + } + + // Log similar message as in abci DistributeValidatorRewards function. + ctx.Logger().Info( + "Distributing ZRC20 Validator Rewards", + "Total", amount.String(), + "Fee_collector", authtypes.FeeCollectorName, + "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), + ) + + if err := c.addDistributeLog(ctx, evm.StateDB, caller, zrc20Addr, amount); err != nil { + return nil, &precompiletypes.ErrUnexpected{ + When: "AddDistributeLog", + Got: err.Error(), + } + } + + return method.Outputs.Pack(true) +} + +func unpackDistributeArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { + zrc20Addr, ok := args[0].(common.Address) + if !ok { + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ + Got: zrc20Addr.String(), + } + } + + amount, ok = args[1].(*big.Int) + if !ok || amount == nil || amount.Sign() <= 0 { + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ + Got: amount.String(), + } + } + + return zrc20Addr, amount, nil +} diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go new file mode 100644 index 0000000000..dddb467b74 --- /dev/null +++ b/precompiles/staking/method_distribute_test.go @@ -0,0 +1,283 @@ +package staking + +import ( + "math/big" + "testing" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func Test_Distribute(t *testing.T) { + feeCollectorAddress := authtypes.NewModuleAddress(authtypes.FeeCollectorName).String() + + t.Run("should fail to run distribute as read only method", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method as read only. + result, err := s.contract.Run(s.mockEVM, s.mockVMContract, true) + + // Check error and result. + require.ErrorIs(t, err, precompiletypes.ErrWriteMethod{ + Method: DistributeMethodName, + }) + + // Result is empty as the write check is done before executing distribute() function. + // On-chain this would look like reverting, so staticcall is properly reverted. + require.Empty(t, result) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should fail to distribute with 0 token balance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.ErrorAs( + t, + precompiletypes.ErrInvalidAmount{ + Got: "0", + }, + err, + ) + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should fail to distribute with 0 allowance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Set caller balance. + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid allowance, got 0") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should fail to distribute 0 token", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Set caller balance. + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(1000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid token amount: 0") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should fail to distribute more than allowed to staking", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Set caller balance. + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(999)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid allowance, got 999, wanted 1000") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should fail to distribute more than user balance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) + + // Set caller balance. + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(100000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1001)}..., + ) + + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "execution reverted") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) + }) + + t.Run("should distribute and lock ZRC20", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(1000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.NoError(t, err) + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.True(t, ok) + }) +} diff --git a/precompiles/staking/method_get_all_validators.go b/precompiles/staking/method_get_all_validators.go new file mode 100644 index 0000000000..2187f2858a --- /dev/null +++ b/precompiles/staking/method_get_all_validators.go @@ -0,0 +1,27 @@ +package staking + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" +) + +func (c *Contract) GetAllValidators( + ctx sdk.Context, + method *abi.Method, +) ([]byte, error) { + validators := c.stakingKeeper.GetAllValidators(ctx) + + validatorsRes := make([]Validator, len(validators)) + for i, v := range validators { + validatorsRes[i] = Validator{ + OperatorAddress: v.OperatorAddress, + ConsensusPubKey: v.ConsensusPubkey.String(), + // Safe casting from int32 to uint8, as BondStatus is an enum. + // #nosec G115 always in range + BondStatus: uint8(v.Status), + Jailed: v.Jailed, + } + } + + return method.Outputs.Pack(validatorsRes) +} diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go new file mode 100644 index 0000000000..8a80793de3 --- /dev/null +++ b/precompiles/staking/method_get_all_validators_test.go @@ -0,0 +1,57 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_GetAllValidators(t *testing.T) { + t.Run("should return empty array if validators not set", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + + // Clean all validators. + validatorsList := s.sdkKeepers.StakingKeeper.GetAllValidators(s.ctx) + for _, v := range validatorsList { + s.sdkKeepers.StakingKeeper.RemoveValidator(s.ctx, v.GetOperator()) + } + + methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + s.mockVMContract.Input = methodID.ID + + // ACT + validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.NoError(t, err) + + res, err := methodID.Outputs.Unpack(validators) + require.NoError(t, err) + + require.Empty(t, res[0]) + }) + + t.Run("should return validators if set", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + s.mockVMContract.Input = methodID.ID + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // ACT + validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.NoError(t, err) + + res, err := methodID.Outputs.Unpack(validators) + require.NoError(t, err) + + require.NotEmpty(t, res[0]) + }) +} diff --git a/precompiles/staking/method_get_shares.go b/precompiles/staking/method_get_shares.go new file mode 100644 index 0000000000..fcf6ce7d1f --- /dev/null +++ b/precompiles/staking/method_get_shares.go @@ -0,0 +1,50 @@ +package staking + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) GetShares( + ctx sdk.Context, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + }) + } + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[0], + } + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[1], + } + } + + validator, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return nil, err + } + + delegation := c.stakingKeeper.Delegation(ctx, sdk.AccAddress(stakerAddress.Bytes()), validator) + shares := big.NewInt(0) + if delegation != nil { + shares = delegation.GetShares().BigInt() + } + + return method.Outputs.Pack(shares) +} diff --git a/precompiles/staking/method_get_shares_test.go b/precompiles/staking/method_get_shares_test.go new file mode 100644 index 0000000000..d0038886f4 --- /dev/null +++ b/precompiles/staking/method_get_shares_test.go @@ -0,0 +1,115 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_GetShares(t *testing.T) { + // Disabled temporarily because the staking functions were disabled. + // Issue: https://github.com/zeta-chain/node/issues/3009 + // t.Run("should return stakes", func(t *testing.T) { + // // ARRANGE + // s := newTestSuite(t) + // methodID := s.contractABI.Methods[GetSharesMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // stakeMethodID := s.contractABI.Methods[StakeMethodName] + + // // ACT + // _, err = s.contract.Stake(s.ctx, s.mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) + // require.NoError(t, err) + + // // ASSERT + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + // s.mockVMContract.Input = packInputArgs(t, methodID, args...) + // stakes, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + // require.NoError(t, err) + + // res, err := methodID.Outputs.Unpack(stakes) + // require.NoError(t, err) + // require.Equal( + // t, + // fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), + // res[0].(*big.Int).String(), + // ) + // }) + + t.Run("should fail if wrong args amount", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr} + + // ACT + _, err := s.contract.GetShares(s.ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid staker arg", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + args := []interface{}{42, validator.OperatorAddress} + + // ACT + _, err := s.contract.GetShares(s.ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid val address", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr, staker.String()} + + // ACT + _, err := s.contract.GetShares(s.ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid val address format", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr, 42} + + // ACT + _, err := s.contract.GetShares(s.ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) +} diff --git a/precompiles/staking/method_move_stake.go b/precompiles/staking/method_move_stake.go new file mode 100644 index 0000000000..71c43bdd7d --- /dev/null +++ b/precompiles/staking/method_move_stake.go @@ -0,0 +1,85 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) MoveStake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 4 { + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 4, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorSrcAddress, ok := args[1].(string) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[1], + } + } + + validatorDstAddress, ok := args[2].(string) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[2], + } + } + + amount, ok := args[3].(*big.Int) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[3], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + res, err := msgServer.BeginRedelegate(ctx, &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorSrcAddress: validatorSrcAddress, + ValidatorDstAddress: validatorDstAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) + err = c.addMoveStakeLog(ctx, stateDB, stakerAddress, validatorSrcAddress, validatorDstAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) +} diff --git a/precompiles/staking/method_move_stake_test.go b/precompiles/staking/method_move_stake_test.go new file mode 100644 index 0000000000..8882442069 --- /dev/null +++ b/precompiles/staking/method_move_stake_test.go @@ -0,0 +1,481 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_MoveStake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[MoveStakeMethodName] + r := rand.New(rand.NewSource(42)) + validatorSrc := sample.Validator(t, r) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validatorSrc) + validatorDest := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + s.mockVMContract.CallerAddress = stakerAddr + + argsStake := []interface{}{ + stakerEthAddr, + validatorSrc.OperatorAddress, + coins.AmountOf(config.BaseDenom).BigInt(), + } + + // stake to validator src + stakeMethodID := s.contractABI.Methods[StakeMethodName] + s.mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ + Method: StakeMethodName, + }) + + argsMoveStake := []interface{}{ + stakerEthAddr, + validatorSrc.OperatorAddress, + validatorDest.OperatorAddress, + coins.AmountOf(config.BaseDenom).BigInt(), + } + s.mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // ACT + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ + Method: MoveStakeMethodName, + }) + }) + + // t.Run("should fail in read only method", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) + // }) + + // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should move stake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // // move stake to validator dest + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if staker is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // 42, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // 42, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // 42, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).Uint64(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // mockVMContract.CallerAddress = callerEthAddr + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker") + // }) +} diff --git a/precompiles/staking/method_stake.go b/precompiles/staking/method_stake.go new file mode 100644 index 0000000000..c19fa6c618 --- /dev/null +++ b/precompiles/staking/method_stake.go @@ -0,0 +1,84 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) Stake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 3 { + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 3, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[1], + } + } + + amount, ok := args[2].(*big.Int) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[2], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + _, err := msgServer.Delegate(ctx, &stakingtypes.MsgDelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + // if caller is not the same as origin it means call is coming through smart contract, + // and because state of smart contract calling precompile might be updated as well + // manually reduce amount in stateDB, so it is properly reflected in bank module + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) + if contract.CallerAddress != evm.Origin { + stateDB.SubBalance(stakerAddress, amount) + } + + err = c.addStakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/method_stake_test.go b/precompiles/staking/method_stake_test.go new file mode 100644 index 0000000000..bd5558abdb --- /dev/null +++ b/precompiles/staking/method_stake_test.go @@ -0,0 +1,316 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_Stake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[StakeMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + s.mockVMContract.CallerAddress = stakerAddr + args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + s.mockVMContract.Input = packInputArgs(t, methodID, args...) + + // ACT + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ + Method: StakeMethodName, + }) + }) + + // t.Run("should fail in read only mode", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) + // }) + + // t.Run("should fail if validator doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should stake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if no input args", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // mockVMContract.Input = methodID.ID + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker address") + // }) + + // t.Run("should fail if staking fails", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // // staker without funds + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err := contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if staker is not eth addr", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator is not valid string", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is not int64", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) +} diff --git a/precompiles/staking/method_unstake.go b/precompiles/staking/method_unstake.go new file mode 100644 index 0000000000..4efb41993b --- /dev/null +++ b/precompiles/staking/method_unstake.go @@ -0,0 +1,77 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) Unstake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 3 { + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 3, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[1], + } + } + + amount, ok := args[2].(*big.Int) + if !ok { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[2], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + res, err := msgServer.Undelegate(ctx, &stakingtypes.MsgUndelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) + err = c.addUnstakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) +} diff --git a/precompiles/staking/method_unstake_test.go b/precompiles/staking/method_unstake_test.go new file mode 100644 index 0000000000..e770020946 --- /dev/null +++ b/precompiles/staking/method_unstake_test.go @@ -0,0 +1,311 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_Unstake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + methodID := s.contractABI.Methods[UnstakeMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + s.mockVMContract.CallerAddress = stakerAddr + + args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + s.mockVMContract.Input = packInputArgs(t, methodID, args...) + + // ACT + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ + Method: UnstakeMethodName, + }) + }) + + // t.Run("should fail in read only method", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) + // }) + + // t.Run("should fail if validator doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should unstake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // stake first + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // // ACT + // mockVMContract.Input = packInputArgs(t, methodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // // stake first + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // mockVMContract.CallerAddress = callerEthAddr + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker address") + // }) + + // t.Run("should fail if no previous staking", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if staker is not eth addr", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator is not valid string", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is not int64", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) +} diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index d29082e545..3408a2b09e 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -1,32 +1,19 @@ package staking import ( - "fmt" - "math/big" - - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - ptypes "github.com/zeta-chain/node/precompiles/types" -) - -// method names -const ( - // write - StakeMethodName = "stake" - UnstakeMethodName = "unstake" - MoveStakeMethodName = "moveStake" - - // read - GetAllValidatorsMethodName = "getAllValidators" - GetSharesMethodName = "getShares" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) var ( @@ -54,11 +41,13 @@ func initABI() { // just temporary flat values, double check these flat values // can we just use WriteCostFlat/ReadCostFlat from gas config for flat values? case StakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = StakeMethodGas case UnstakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = UnstakeMethodGas case MoveStakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = MoveStakeMethodGas + case DistributeMethodName: + GasRequiredByMethod[methodID] = DistributeMethodGas case GetAllValidatorsMethodName: GasRequiredByMethod[methodID] = 0 ViewMethod[methodID] = true @@ -72,23 +61,45 @@ func initABI() { } type Contract struct { - ptypes.BaseContract - - stakingKeeper stakingkeeper.Keeper - cdc codec.Codec - kvGasConfig storetypes.GasConfig + precompiletypes.BaseContract + + stakingKeeper stakingkeeper.Keeper + fungibleKeeper fungiblekeeper.Keeper + bankKeeper bankkeeper.Keeper + zrc20ABI *abi.ABI + cdc codec.Codec + kvGasConfig storetypes.GasConfig } func NewIStakingContract( + ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, + fungibleKeeper fungiblekeeper.Keeper, + bankKeeper bankkeeper.Keeper, cdc codec.Codec, kvGasConfig storetypes.GasConfig, ) *Contract { + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + if fungibleKeeper.GetAuthKeeper().GetAccount(ctx, accAddress) == nil { + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + } + + // Instantiate the ZRC20 ABI only one time. + // This avoids instantiating it every time deposit or withdraw are called. + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + ctx.Logger().Error("staking contract failed to get ZRC20 ABI", "error", err) + return nil + } + return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), - stakingKeeper: *stakingKeeper, - cdc: cdc, - kvGasConfig: kvGasConfig, + BaseContract: precompiletypes.NewBaseContract(ContractAddress), + stakingKeeper: *stakingKeeper, + fungibleKeeper: fungibleKeeper, + bankKeeper: bankKeeper, + zrc20ABI: zrc20ABI, + cdc: cdc, + kvGasConfig: kvGasConfig, } } @@ -122,263 +133,6 @@ func (c *Contract) RequiredGas(input []byte) uint64 { return 0 } -func (c *Contract) GetAllValidators( - ctx sdk.Context, - method *abi.Method, -) ([]byte, error) { - validators := c.stakingKeeper.GetAllValidators(ctx) - - validatorsRes := make([]Validator, len(validators)) - for i, v := range validators { - validatorsRes[i] = Validator{ - OperatorAddress: v.OperatorAddress, - ConsensusPubKey: v.ConsensusPubkey.String(), - // #nosec G115 enum always in range - BondStatus: uint8(v.Status), - Jailed: v.Jailed, - } - } - - return method.Outputs.Pack(validatorsRes) -} - -func (c *Contract) GetShares( - ctx sdk.Context, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 2, - }) - } - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - validator, err := sdk.ValAddressFromBech32(validatorAddress) - if err != nil { - return nil, err - } - - delegation := c.stakingKeeper.Delegation(ctx, sdk.AccAddress(stakerAddress.Bytes()), validator) - shares := big.NewInt(0) - if delegation != nil { - shares = delegation.GetShares().BigInt() - } - - return method.Outputs.Pack(shares) -} - -func (c *Contract) Stake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 3, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - amount, ok := args[2].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - _, err := msgServer.Delegate(ctx, &stakingtypes.MsgDelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorAddress: validatorAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - // if caller is not the same as origin it means call is coming through smart contract, - // and because state of smart contract calling precompile might be updated as well - // manually reduce amount in stateDB, so it is properly reflected in bank module - stateDB := evm.StateDB.(ptypes.ExtStateDB) - if contract.CallerAddress != evm.Origin { - stateDB.SubBalance(stakerAddress, amount) - } - - err = c.AddStakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(true) -} - -func (c *Contract) Unstake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 3, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - amount, ok := args[2].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - res, err := msgServer.Undelegate(ctx, &stakingtypes.MsgUndelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorAddress: validatorAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - stateDB := evm.StateDB.(ptypes.ExtStateDB) - err = c.AddUnstakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) -} - -func (c *Contract) MoveStake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 4 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 4, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorSrcAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - validatorDstAddress, ok := args[2].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - amount, ok := args[3].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[3], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - res, err := msgServer.BeginRedelegate(ctx, &stakingtypes.MsgBeginRedelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorSrcAddress: validatorSrcAddress, - ValidatorDstAddress: validatorDstAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - stateDB := evm.StateDB.(ptypes.ExtStateDB) - err = c.AddMoveStakeLog(ctx, stateDB, stakerAddress, validatorSrcAddress, validatorDstAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) -} - // Run is the entrypoint of the precompiled contract, it switches over the input method, // and execute them accordingly. func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { @@ -392,7 +146,14 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) + + // If the method is not a view method, it should not be executed in read-only mode. + if _, isViewMethod := ViewMethod[[4]byte(method.ID)]; !isViewMethod && readOnly { + return nil, precompiletypes.ErrWriteMethod{ + Method: method.Name, + } + } switch method.Name { case GetAllValidatorsMethodName: @@ -417,17 +178,11 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case StakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Stake(ctx, evm, contract, method, args) @@ -439,17 +194,11 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case UnstakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Unstake(ctx, evm, contract, method, args) @@ -461,17 +210,11 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case MoveStakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.MoveStake(ctx, evm, contract, method, args) @@ -481,9 +224,23 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } return res, nil + case DistributeMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.distribute(ctx, evm, contract, method, args) + return err + }) + if execErr != nil { + res, errPack := method.Outputs.Pack(false) + if errPack != nil { + return nil, errPack + } + return res, err + } + return res, nil default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index aaea87f94d..b7b00ede0d 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -2,17 +2,16 @@ package staking import ( "encoding/json" - "fmt" "testing" "math/big" - "math/rand" tmdb "github.com/cometbft/cometbft-db" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -20,103 +19,39 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ethermint "github.com/zeta-chain/ethermint/types" + evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" "github.com/zeta-chain/ethermint/x/evm/statedb" "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" "github.com/zeta-chain/node/precompiles/prototype" - ptypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/keeper" - "github.com/zeta-chain/node/testutil/sample" + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" fungibletypes "github.com/zeta-chain/node/x/fungible/types" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" ) -func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { - var encoding ethermint.EncodingConfig - appCodec := encoding.Codec - - cdc := keeper.NewCodec() - - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) - keys, memKeys, tkeys, allKeys := keeper.StoreKeys() - sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) - for _, key := range keys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) - } - for _, key := range tkeys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) - } - for _, key := range memKeys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) - } - - gasConfig := storetypes.TransientGasConfig() - ctx := keeper.NewContext(stateStore) - - require.NoError(t, stateStore.LoadLatestVersion()) - - stakingGenesisState := stakingtypes.DefaultGenesisState() - stakingGenesisState.Params.BondDenom = config.BaseDenom - sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) - - contract := NewIStakingContract(&sdkKeepers.StakingKeeper, appCodec, gasConfig) - require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") - - abi := contract.Abi() - require.NotNil(t, abi, "contract ABI should not be nil") - - address := contract.Address() - require.NotNil(t, address, "contract address should not be nil") - - mockEVM := vm.NewEVM( - vm.BlockContext{}, - vm.TxContext{}, - statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), - ¶ms.ChainConfig{}, - vm.Config{}, - ) - mockVMContract := vm.NewContract( - contractRef{address: common.Address{}}, - contractRef{address: ContractAddress}, - big.NewInt(0), - 0, - ) - return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract -} - -func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { - input, err := methodID.Inputs.Pack(args...) - require.NoError(t, err) - return append(methodID.ID, input...) -} - -type contractRef struct { - address common.Address -} - -func (c contractRef) Address() common.Address { - return c.address -} - func Test_IStakingContract(t *testing.T) { - _, contract, abi, _, _, _ := setup(t) + s := newTestSuite(t) gasConfig := storetypes.TransientGasConfig() t.Run("should check methods are present in ABI", func(t *testing.T) { - require.NotNil(t, abi.Methods[StakeMethodName], "stake method should be present in the ABI") - require.NotNil(t, abi.Methods[UnstakeMethodName], "unstake method should be present in the ABI") + require.NotNil(t, s.contractABI.Methods[StakeMethodName], "stake method should be present in the ABI") + require.NotNil(t, s.contractABI.Methods[UnstakeMethodName], "unstake method should be present in the ABI") require.NotNil( t, - abi.Methods[MoveStakeMethodName], + s.contractABI.Methods[MoveStakeMethodName], "moveStake method should be present in the ABI", ) require.NotNil( t, - abi.Methods[GetAllValidatorsMethodName], + s.contractABI.Methods[GetAllValidatorsMethodName], "getAllValidators method should be present in the ABI", ) - require.NotNil(t, abi.Methods[GetSharesMethodName], "getShares method should be present in the ABI") + require.NotNil(t, s.contractABI.Methods[GetSharesMethodName], "getShares method should be present in the ABI") }) t.Run("should check gas requirements for methods", func(t *testing.T) { @@ -124,9 +59,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("stake", func(t *testing.T) { // ACT - stake := contract.RequiredGas(abi.Methods[StakeMethodName].ID) + stake := s.contract.RequiredGas(s.contractABI.Methods[StakeMethodName].ID) // ASSERT - copy(method[:], abi.Methods[StakeMethodName].ID[:4]) + copy(method[:], s.contractABI.Methods[StakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -140,9 +75,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("unstake", func(t *testing.T) { // ACT - unstake := contract.RequiredGas(abi.Methods[UnstakeMethodName].ID) + unstake := s.contract.RequiredGas(s.contractABI.Methods[UnstakeMethodName].ID) // ASSERT - copy(method[:], abi.Methods[UnstakeMethodName].ID[:4]) + copy(method[:], s.contractABI.Methods[UnstakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -156,9 +91,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("moveStake", func(t *testing.T) { // ACT - moveStake := contract.RequiredGas(abi.Methods[MoveStakeMethodName].ID) + moveStake := s.contract.RequiredGas(s.contractABI.Methods[MoveStakeMethodName].ID) // ASSERT - copy(method[:], abi.Methods[MoveStakeMethodName].ID[:4]) + copy(method[:], s.contractABI.Methods[MoveStakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -172,9 +107,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("getAllValidators", func(t *testing.T) { // ACT - getAllValidators := contract.RequiredGas(abi.Methods[GetAllValidatorsMethodName].ID) + getAllValidators := s.contract.RequiredGas(s.contractABI.Methods[GetAllValidatorsMethodName].ID) // ASSERT - copy(method[:], abi.Methods[GetAllValidatorsMethodName].ID[:4]) + copy(method[:], s.contractABI.Methods[GetAllValidatorsMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte require.Equal( t, @@ -188,9 +123,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("getShares", func(t *testing.T) { // ACT - getShares := contract.RequiredGas(abi.Methods[GetSharesMethodName].ID) + getShares := s.contract.RequiredGas(s.contractABI.Methods[GetSharesMethodName].ID) // ASSERT - copy(method[:], abi.Methods[GetSharesMethodName].ID[:4]) + copy(method[:], s.contractABI.Methods[GetSharesMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte require.Equal( t, @@ -206,7 +141,7 @@ func Test_IStakingContract(t *testing.T) { // ARRANGE invalidMethodBytes := []byte("invalidMethod") // ACT - gasInvalidMethod := contract.RequiredGas(invalidMethodBytes) + gasInvalidMethod := s.contract.RequiredGas(invalidMethodBytes) // ASSERT require.Equal( t, @@ -221,9 +156,9 @@ func Test_IStakingContract(t *testing.T) { } func Test_InvalidMethod(t *testing.T) { - _, _, abi, _, _, _ := setup(t) + s := newTestSuite(t) - _, doNotExist := abi.Methods["invalidMethod"] + _, doNotExist := s.contractABI.Methods["invalidMethod"] require.False(t, doNotExist, "invalidMethod should not be present in the ABI") } @@ -238,1238 +173,379 @@ func Test_InvalidABI(t *testing.T) { initABI() } -func Test_Stake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[StakeMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) - - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: StakeMethodName, - }) - }) +func Test_RunInvalidMethod(t *testing.T) { + // ARRANGE + s := newTestSuite(t) - // t.Run("should fail in read only mode", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) - // }) - - // t.Run("should fail if validator doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should stake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if no input args", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // mockVMContract.Input = methodID.ID - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker address") - // }) - - // t.Run("should fail if staking fails", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // // staker without funds - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err := contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if staker is not eth addr", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator is not valid string", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is not int64", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) -} + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() -func Test_Unstake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[UnstakeMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - - args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) - - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: UnstakeMethodName, - }) - }) + prototype := prototype.NewIPrototypeContract(s.fungibleKeeper, appCodec, gasConfig) + + prototypeAbi := prototype.Abi() + methodID := prototypeAbi.Methods["bech32ToHexAddr"] + args := []interface{}{"123"} + s.mockVMContract.Input = packInputArgs(t, methodID, args...) - // t.Run("should fail in read only method", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) - // }) - - // t.Run("should fail if validator doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should unstake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // stake first - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // // ACT - // mockVMContract.Input = packInputArgs(t, methodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // // stake first - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // mockVMContract.CallerAddress = callerEthAddr - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker address") - // }) - - // t.Run("should fail if no previous staking", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if staker is not eth addr", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator is not valid string", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is not int64", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) + // ACT + _, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.Error(t, err) } -func Test_MoveStake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[MoveStakeMethodName] - r := rand.New(rand.NewSource(42)) - validatorSrc := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - validatorDest := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - - argsStake := []interface{}{ - stakerEthAddr, - validatorSrc.OperatorAddress, - coins.AmountOf(config.BaseDenom).BigInt(), - } +func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { + // Initialize state. + // Get sdk keepers initialized with this state and the context. + cdc := keeper.NewCodec() + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + keys, memKeys, tkeys, allKeys := keeper.StoreKeys() - // stake to validator src - stakeMethodID := abi.Methods[StakeMethodName] - mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - _, err = contract.Run(mockEVM, mockVMContract, false) - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: StakeMethodName, - }) + sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) - argsMoveStake := []interface{}{ - stakerEthAddr, - validatorSrc.OperatorAddress, - validatorDest.OperatorAddress, - coins.AmountOf(config.BaseDenom).BigInt(), - } - mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + for _, key := range keys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) + } + for _, key := range tkeys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) + } + for _, key := range memKeys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) + } - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) + require.NoError(t, stateStore.LoadLatestVersion()) - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: MoveStakeMethodName, - }) - }) + ctx := keeper.NewContext(stateStore) - // t.Run("should fail in read only method", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) - // }) - - // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should move stake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // // move stake to validator dest - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if staker is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // 42, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // 42, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // 42, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).Uint64(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // mockVMContract.CallerAddress = callerEthAddr - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker") - // }) + // Intiliaze codecs and gas config. + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() + + stakingGenesisState := stakingtypes.DefaultGenesisState() + stakingGenesisState.Params.BondDenom = config.BaseDenom + sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) + + // Get the fungible keeper. + fungibleKeeper, _, _, _ := keeper.FungibleKeeper(t) + + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + + // Initialize staking contract. + contract := NewIStakingContract( + ctx, + &sdkKeepers.StakingKeeper, + *fungibleKeeper, + sdkKeepers.BankKeeper, + appCodec, + gasConfig, + ) + require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") + + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") + + address := contract.Address() + require.NotNil(t, address, "contract address should not be nil") + + mockEVM := vm.NewEVM( + vm.BlockContext{}, + vm.TxContext{}, + statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), + ¶ms.ChainConfig{}, + vm.Config{}, + ) + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: ContractAddress}, + big.NewInt(0), + 0, + ) + + return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract } -func Test_GetAllValidators(t *testing.T) { - t.Run("should return empty array if validators not set", func(t *testing.T) { - // ARRANGE - _, contract, abi, _, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetAllValidatorsMethodName] - mockVMContract.Input = methodID.ID +/* + Complete Test Suite + TODO: Migrate all staking tests to this suite. +*/ + +type testSuite struct { + ctx sdk.Context + contract *Contract + contractABI *abi.ABI + fungibleKeeper *fungiblekeeper.Keeper + sdkKeepers keeper.SDKKeepers + mockEVM *vm.EVM + mockVMContract *vm.Contract + methodID abi.Method + defaultCaller common.Address + defaultLocker common.Address + zrc20Address common.Address + zrc20ABI *abi.ABI +} - // ACT - validators, err := contract.Run(mockEVM, mockVMContract, false) +func newTestSuite(t *testing.T) testSuite { + // Initialize basic parameters to mock the chain. + fungibleKeeper, ctx, sdkKeepers, _ := keeper.FungibleKeeper(t) + chainID := getValidChainID(t) - // ASSERT - require.NoError(t, err) + // Make sure the account store is initialized. + // This is completely needed for accounts to be created in the state. + fungibleKeeper.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) - res, err := methodID.Outputs.Unpack(validators) - require.NoError(t, err) + // Deploy system contracts in order to deploy a ZRC20 token. + deploySystemContracts(t, ctx, fungibleKeeper, *sdkKeepers.EvmKeeper) + zrc20Address := setupGasCoin(t, ctx, fungibleKeeper, sdkKeepers.EvmKeeper, chainID, "ZRC20", "ZRC20") - require.Empty(t, res[0]) - }) + // Keepers and chain configuration. + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() - t.Run("should return validators if set", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetAllValidatorsMethodName] - mockVMContract.Input = methodID.ID - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + // Create the staking contract. + contract := NewIStakingContract( + ctx, + &sdkKeepers.StakingKeeper, + *fungibleKeeper, + sdkKeepers.BankKeeper, + appCodec, + gasConfig, + ) + require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") - // ACT - validators, err := contract.Run(mockEVM, mockVMContract, false) + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) - // ASSERT - require.NoError(t, err) + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") - res, err := methodID.Outputs.Unpack(validators) - require.NoError(t, err) + address := contract.Address() + require.NotNil(t, address, "contract address should not be nil") - require.NotEmpty(t, res[0]) - }) + mockEVM := vm.NewEVM( + vm.BlockContext{}, + vm.TxContext{}, + statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), + ¶ms.ChainConfig{}, + vm.Config{}, + ) + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: ContractAddress}, + big.NewInt(0), + 0, + ) + + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + require.NoError(t, err) + + // Default locker is the bank address. + locker := common.HexToAddress("0x0000000000000000000000000000000000000067") + + // Set default caller. + caller := fungibletypes.ModuleAddressEVM + mockVMContract.CallerAddress = caller + mockEVM.Origin = caller + + return testSuite{ + ctx, + contract, + &abi, + fungibleKeeper, + sdkKeepers, + mockEVM, + mockVMContract, + abi.Methods[DistributeMethodName], + caller, + locker, + zrc20Address, + zrc20ABI, + } } -func Test_GetShares(t *testing.T) { - t.Run("should return stakes", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetSharesMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - - stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - stakeMethodID := abi.Methods[StakeMethodName] - - // ACT - _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) - require.NoError(t, err) - - // ASSERT - args := []interface{}{stakerEthAddr, validator.OperatorAddress} - mockVMContract.Input = packInputArgs(t, methodID, args...) - stakes, err := contract.Run(mockEVM, mockVMContract, false) - require.NoError(t, err) - - res, err := methodID.Outputs.Unpack(stakes) - require.NoError(t, err) - require.Equal( - t, - fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), - res[0].(*big.Int).String(), - ) - }) +func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { + input, err := methodID.Inputs.Pack(args...) + require.NoError(t, err) + return append(methodID.ID, input...) +} - t.Run("should fail if wrong args amount", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr} +func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { + resAllowance, err := callEVM( + t, + ts.ctx, + ts.fungibleKeeper, + ts.zrc20ABI, + fungibletypes.ModuleAddressEVM, + ts.zrc20Address, + "approve", + []interface{}{ts.contract.Address(), amount}, + ) + require.NoError(t, err, "error allowing staking to spend ZRC20 tokens") - // ACT - _, err := contract.GetShares(ctx, &methodID, args) + allowed, ok := resAllowance[0].(bool) + require.True(t, ok) + require.True(t, allowed) +} - // ASSERT - require.Error(t, err) - }) +func callEVM( + t *testing.T, + ctx sdk.Context, + fungibleKeeper *fungiblekeeper.Keeper, + abi *abi.ABI, + from common.Address, + dst common.Address, + method string, + args []interface{}, +) ([]interface{}, error) { + res, err := fungibleKeeper.CallEVM( + ctx, // ctx + *abi, // abi + from, // from + dst, // to + big.NewInt(0), // value + nil, // gasLimit + true, // commit + true, // noEthereumTxEvent + method, // method + args..., // args + ) + require.NoError(t, err, "CallEVM error") + require.Equal(t, "", res.VmError, "res.VmError should be empty") - t.Run("should fail if invalid staker arg", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - args := []interface{}{42, validator.OperatorAddress} + ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) + require.NoError(t, err, "Unpack error") - // ACT - _, err := contract.GetShares(ctx, &methodID, args) + return ret, nil +} - // ASSERT - require.Error(t, err) - }) +// setupGasCoin is a helper function to setup the gas coin for testing +func setupGasCoin( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + chainID int64, + assetName string, + symbol string, +) (zrc20 common.Address) { + addr, err := k.SetupChainGasCoinAndPool( + ctx, + chainID, + assetName, + symbol, + 8, + nil, + ) + require.NoError(t, err) + assertContractDeployment(t, *evmk, ctx, addr) + return addr +} + +// get a valid chain id independently of the build flag +func getValidChainID(t *testing.T) int64 { + list := chains.DefaultChainsList() + require.NotEmpty(t, list) + require.NotNil(t, list[0]) + return list[0].ChainId +} - t.Run("should fail if invalid val address", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr, staker.String()} +// require that a contract has been deployed by checking stored code is non-empty. +func assertContractDeployment(t *testing.T, k evmkeeper.Keeper, ctx sdk.Context, contractAddress common.Address) { + acc := k.GetAccount(ctx, contractAddress) + require.NotNil(t, acc) + code := k.GetCode(ctx, common.BytesToHash(acc.CodeHash)) + require.NotEmpty(t, code) +} - // ACT - _, err := contract.GetShares(ctx, &methodID, args) +// deploySystemContracts deploys the system contracts and returns their addresses. +func deploySystemContracts( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk evmkeeper.Keeper, +) (wzeta, uniswapV2Factory, uniswapV2Router, connector, systemContract common.Address) { + var err error - // ASSERT - require.Error(t, err) - }) + wzeta, err = k.DeployWZETA(ctx) + require.NoError(t, err) + require.NotEmpty(t, wzeta) + assertContractDeployment(t, evmk, ctx, wzeta) - t.Run("should fail if invalid val address format", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr, 42} + uniswapV2Factory, err = k.DeployUniswapV2Factory(ctx) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Factory) + assertContractDeployment(t, evmk, ctx, uniswapV2Factory) - // ACT - _, err := contract.GetShares(ctx, &methodID, args) + uniswapV2Router, err = k.DeployUniswapV2Router02(ctx, uniswapV2Factory, wzeta) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Router) + assertContractDeployment(t, evmk, ctx, uniswapV2Router) - // ASSERT - require.Error(t, err) - }) + connector, err = k.DeployConnectorZEVM(ctx, wzeta) + require.NoError(t, err) + require.NotEmpty(t, connector) + assertContractDeployment(t, evmk, ctx, connector) + + systemContract, err = k.DeploySystemContract(ctx, wzeta, uniswapV2Factory, uniswapV2Router) + require.NoError(t, err) + 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 } -func Test_RunInvalidMethod(t *testing.T) { - // ARRANGE - _, contract, _, _, mockEVM, mockVMContract := setup(t) - k, _, _, _ := keeper.FungibleKeeper(t) +// 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) - var encoding ethermint.EncodingConfig - appCodec := encoding.Codec - gasConfig := storetypes.TransientGasConfig() + // Deploy the proxy contract + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(t, err) - prototype := prototype.NewIPrototypeContract(k, appCodec, gasConfig) + // Encode the initializer data + initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) + require.NoError(t, err) - prototypeAbi := prototype.Abi() - methodID := prototypeAbi.Methods["bech32ToHexAddr"] - args := []interface{}{"123"} - mockVMContract.Input = packInputArgs(t, methodID, args...) + gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) + require.NoError(t, err) + require.NotEmpty(t, gatewayContract) + assertContractDeployment(t, *evmk, ctx, gatewayContract) - // ACT - _, err := contract.Run(mockEVM, mockVMContract, false) + // 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) - // ASSERT - require.Error(t, err) + return gatewayContract +} + +type contractRef struct { + address common.Address +} + +func (c contractRef) Address() common.Address { + return c.address } diff --git a/precompiles/types/address.go b/precompiles/types/address.go new file mode 100644 index 0000000000..5e6654cc5b --- /dev/null +++ b/precompiles/types/address.go @@ -0,0 +1,48 @@ +package types + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +// GetEVMCallerAddress returns the caller address. +// Usually the caller is the contract.CallerAddress, which is the address of the contract that called the precompiled contract. +// If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, +// on which case there is a need to set the caller to the evm.Origin. +func GetEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, error) { + if evm == nil || contract == nil { + return common.Address{}, errors.New("invalid input: evm or contract is nil") + } + + caller := contract.CallerAddress + if contract.CallerAddress != evm.Origin { + caller = evm.Origin + } + + return caller, nil +} + +// GetCosmosAddress returns the counterpart cosmos address of the given ethereum address. +// It checks if the address is empty or blocked by the bank keeper. +func GetCosmosAddress(bankKeeper bank.Keeper, addr common.Address) (sdk.AccAddress, error) { + toAddr := sdk.AccAddress(addr.Bytes()) + if toAddr.Empty() { + return nil, &ErrInvalidAddr{ + Got: toAddr.String(), + Reason: "empty address", + } + } + + if bankKeeper.BlockedAddr(toAddr) { + return nil, &ErrInvalidAddr{ + Got: toAddr.String(), + Reason: "destination address blocked by bank keeper", + } + } + + return toAddr, nil +} diff --git a/precompiles/types/address_test.go b/precompiles/types/address_test.go new file mode 100644 index 0000000000..801df4f8d4 --- /dev/null +++ b/precompiles/types/address_test.go @@ -0,0 +1,68 @@ +package types + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_GetEVMCallerAddress(t *testing.T) { + t.Run("should raise error when evm is nil", func(t *testing.T) { + _, mockVMContract := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(nil, &mockVMContract) + require.Error(t, err) + require.Equal(t, common.Address{}, caller, "address should be zeroed") + }) + + t.Run("should raise error when contract is nil", func(t *testing.T) { + mockEVM, _ := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(&mockEVM, nil) + require.Error(t, err) + require.Equal(t, common.Address{}, caller, "address should be zeroed") + }) + + // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. + t.Run("when caller address equals origin", func(t *testing.T) { + mockEVM, mockVMContract := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(&mockEVM, &mockVMContract) + require.NoError(t, err) + require.Equal(t, common.Address{}, caller, "address should be the same") + }) + + // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. + t.Run("when caller address equals origin", func(t *testing.T) { + mockEVM, mockVMContract := setupMockEVMAndContract(sample.EthAddress()) + caller, err := GetEVMCallerAddress(&mockEVM, &mockVMContract) + require.NoError(t, err) + require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") + }) +} + +func setupMockEVMAndContract(address common.Address) (vm.EVM, vm.Contract) { + mockEVM := vm.EVM{ + TxContext: vm.TxContext{ + Origin: address, + }, + } + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: common.Address{}}, + big.NewInt(0), + 0, + ) + + return mockEVM, *mockVMContract +} + +type contractRef struct { + address common.Address +} + +func (c contractRef) Address() common.Address { + return c.address +} diff --git a/precompiles/bank/coin.go b/precompiles/types/coin.go similarity index 59% rename from precompiles/bank/coin.go rename to precompiles/types/coin.go index 121ecdc1ee..4219040d42 100644 --- a/precompiles/bank/coin.go +++ b/precompiles/types/coin.go @@ -1,4 +1,4 @@ -package bank +package types import ( "math/big" @@ -7,19 +7,27 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - ptypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/cmd/zetacored/config" ) // ZRC20ToCosmosDenom returns the cosmos coin address for a given ZRC20 address. -// This is converted to "zevm/{ZRC20Address}". +// This is converted to "zrc20/{ZRC20Address}". func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { - return ZRC20DenomPrefix + ZRC20Address.String() + return config.ZRC20DenomPrefix + ZRC20Address.String() } -func createCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { - coin := sdk.NewCoin(tokenDenom, math.NewIntFromBigInt(amount)) +func CreateCoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, error) { + defer func() { + if r := recover(); r != nil { + return + } + }() + + denom := ZRC20ToCosmosDenom(zrc20address) + + coin := sdk.NewCoin(denom, math.NewIntFromBigInt(amount)) if !coin.IsValid() { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &ErrInvalidCoin{ Got: coin.GetDenom(), Negative: coin.IsNegative(), Nil: coin.IsNil(), @@ -28,10 +36,10 @@ func createCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { // A sdk.Coins (type []sdk.Coin) has to be created because it's the type expected by MintCoins // and SendCoinsFromModuleToAccount. - // But sdk.Coins will only contain one coin, always. + // But coinSet will only contain one coin, always. coinSet := sdk.NewCoins(coin) if !coinSet.IsValid() || coinSet.Empty() || coinSet.IsAnyNil() || coinSet == nil { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &ErrInvalidCoin{ Got: coinSet.String(), Negative: coinSet.IsAnyNegative(), Nil: coinSet.IsAnyNil(), diff --git a/precompiles/bank/coin_test.go b/precompiles/types/coin_test.go similarity index 81% rename from precompiles/bank/coin_test.go rename to precompiles/types/coin_test.go index 0a9669e0a6..6a8aac7d46 100644 --- a/precompiles/bank/coin_test.go +++ b/precompiles/types/coin_test.go @@ -1,4 +1,4 @@ -package bank +package types import ( "math/big" @@ -16,10 +16,11 @@ func Test_ZRC20ToCosmosDenom(t *testing.T) { } func Test_createCoinSet(t *testing.T) { - tokenDenom := "zrc20/0x0000000000000000000000000000000000003039" + tokenAddr := common.HexToAddress("0x0000000000000000000000000000000000003039") + tokenDenom := ZRC20ToCosmosDenom(tokenAddr) amount := big.NewInt(100) - coinSet, err := createCoinSet(tokenDenom, amount) + coinSet, err := CreateCoinSet(tokenAddr, amount) require.NoError(t, err, "createCoinSet should not return an error") require.NotNil(t, coinSet, "coinSet should not be nil") diff --git a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go index f7d152f749..7ac3d7ef22 100644 --- a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go +++ b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go @@ -24,8 +24,9 @@ func (k Keeper) LockZRC20( amount *big.Int, ) error { // owner is the EOA owner of the ZRC20 tokens. + // spender is the EOA allowed to spend ZRC20 on owner's behalf. // locker is the address that will lock the ZRC20 tokens, i.e: bank precompile. - if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, locker, zrc20Address, amount); err != nil { + if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, spender, zrc20Address, amount); err != nil { return errors.Wrap(err, "failed allowance check") } From 04d2b3aafe02a1c3cded50b3871c6949051807b6 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Wed, 30 Oct 2024 04:59:46 -0700 Subject: [PATCH 13/34] chore: set lower tally params for localnet (#3067) --- contrib/localnet/scripts/start-zetacored.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 1f5e2fca12..c0e8424738 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -292,8 +292,13 @@ then echo "Importing data" zetacored parse-genesis-file /root/genesis_data/exported-genesis.json fi -# Update governance voting period to 100s , to ignore the voting period imported from mainnet. - cat $HOME/.zetacored/config/genesis.json | jq '.app_state["gov"]["voting_params"]["voting_period"]="100s"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json + +# Update governance voting parameters for localnet +# this allows for quick upgrades and using more than two nodes + jq '.app_state["gov"]["params"]["voting_period"]="100s" | + .app_state["gov"]["params"]["quorum"]="0.1" | + .app_state["gov"]["params"]["threshold"]="0.1"' \ + $HOME/.zetacored/config/genesis.json > tmp.json && mv tmp.json $HOME/.zetacored/config/genesis.json # 4. Collect all the gentx files in zetacore0 and create the final genesis.json zetacored collect-gentxs From 648d2e3edff8cb920d96713423a5cc532404f964 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Wed, 30 Oct 2024 04:59:57 -0700 Subject: [PATCH 14/34] chore: use v21 as upgrade base (#3063) --- Makefile | 4 ++-- e2e/e2etests/test_eth_withdraw.go | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 8ee617a9d2..61b4c492e5 100644 --- a/Makefile +++ b/Makefile @@ -301,7 +301,7 @@ ifdef UPGRADE_TEST_FROM_SOURCE zetanode-upgrade: zetanode @echo "Building zetanode-upgrade from source" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime-source \ - --build-arg OLD_VERSION='release/v20' \ + --build-arg OLD_VERSION='release/v21' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) . @@ -310,7 +310,7 @@ else zetanode-upgrade: zetanode @echo "Building zetanode-upgrade from binaries" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime \ - --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v20.0.2' \ + --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v21.0.0' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) \ . diff --git a/e2e/e2etests/test_eth_withdraw.go b/e2e/e2etests/test_eth_withdraw.go index 0a9140578b..0ba15299e1 100644 --- a/e2e/e2etests/test_eth_withdraw.go +++ b/e2e/e2etests/test_eth_withdraw.go @@ -39,12 +39,8 @@ func TestEtherWithdraw(r *runner.E2ERunner, args []string) { utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) - // Previous binary doesn't take EIP-1559 into account, so this will fail. - // Thus, we need to skip this check for upgrade tests - if !r.IsRunningUpgrade() { - withdrawalReceipt := mustFetchEthReceipt(r, cctx) - require.Equal(r, uint8(ethtypes.DynamicFeeTxType), withdrawalReceipt.Type, "receipt type mismatch") - } + withdrawalReceipt := mustFetchEthReceipt(r, cctx) + require.Equal(r, uint8(ethtypes.DynamicFeeTxType), withdrawalReceipt.Type, "receipt type mismatch") r.Logger.Info("TestEtherWithdraw completed") } From 0b89266a2534d52fb22655e608a81072a33128e6 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:53:08 +0100 Subject: [PATCH 15/34] feat(zetaclient)!: add support for TON withdrawals (#3020) * Apply changes from recent Gateway refactoring * Move GW logic to pkg/contracts/ton. Fix E2E tests * Implement watchRPCStatus() for TON * Implement watchGasPrice() for TON * Add Withdraw message w/ payload signature * Minor refactoring * Add TON signer * Add withdrawal parsing; Update Gateway with a fix of https://github.com/zeta-chain/protocol-contracts-ton/pull/19 * Add signer tests * Add addOutboundTracker to *observer*; Add TSS cache for signatures * Update GW (https://github.com/zeta-chain/protocol-contracts-ton/pull/20) * Add test cases for withdrawals in observeGateway() * Add observe outbound * Enforce additional validation on withdrawal parsing * Implement TON cctx scheduler * Implement TON VoteOutboundIfConfirmed * TON withdrawal E2E * Update changelog * gosec * Address PR comments [1] * Address PR comments [2] * Address PR comments [3] * Address PR comments [4] * Bump ton-docker image version * gosec * Address PR comments [5] * Rollback to prev. ton-docker * Address PR comments [6] --- changelog.md | 1 + cmd/zetae2e/local/local.go | 1 + contrib/localnet/docker-compose.yml | 2 + e2e/e2etests/e2etests.go | 9 + e2e/e2etests/test_ton_deposit.go | 27 +- e2e/e2etests/test_ton_deposit_and_call.go | 27 +- e2e/e2etests/test_ton_withdrawal.go | 93 +++++ e2e/runner/setup_ton.go | 25 +- e2e/runner/ton.go | 76 ++++- e2e/runner/ton/accounts.go | 78 +---- e2e/runner/ton/accounts_test.go | 45 --- e2e/runner/ton/deployer.go | 6 +- e2e/runner/ton/gateway.compiled.json | 5 - pkg/chains/chain.go | 6 + {e2e/runner => pkg/contracts}/ton/coin.go | 5 +- pkg/contracts/ton/gateway.compiled.json | 5 + pkg/contracts/ton/gateway.go | 226 ++++-------- pkg/contracts/ton/gateway_deploy.go | 74 ++++ pkg/contracts/ton/gateway_msg.go | 215 ++++++++++++ pkg/contracts/ton/gateway_op.go | 115 ------- pkg/contracts/ton/gateway_parse.go | 293 ++++++++++++++++ pkg/contracts/ton/gateway_send.go | 48 ++- pkg/contracts/ton/gateway_test.go | 213 ++++++++++-- pkg/contracts/ton/gateway_tx.go | 11 + pkg/contracts/ton/testdata/00-donation.json | 10 +- pkg/contracts/ton/testdata/01-deposit.json | 12 +- .../ton/testdata/02-deposit-and-call.json | 10 +- .../ton/testdata/04-bounced-msg.json | 12 +- pkg/contracts/ton/testdata/06-withdrawal.json | 8 + pkg/ticker/ticker.go | 8 +- pkg/ticker/ticker_test.go | 2 +- testutil/sample/{sample_ton.go => ton.go} | 110 +++--- .../{sample_ton_test.go => ton_test.go} | 0 zetaclient/chains/base/observer.go | 22 +- zetaclient/chains/ton/config.go | 76 +++++ zetaclient/chains/ton/liteapi/client.go | 27 ++ .../chains/ton/liteapi/client_live_test.go | 52 +++ zetaclient/chains/ton/observer/inbound.go | 100 ++++-- .../chains/ton/observer/inbound_test.go | 77 ++++- zetaclient/chains/ton/observer/observer.go | 161 ++++++++- .../chains/ton/observer/observer_test.go | 78 ++++- zetaclient/chains/ton/observer/outbound.go | 323 ++++++++++++++++++ .../chains/ton/observer/outbound_test.go | 80 +++++ zetaclient/chains/ton/signer/signer.go | 242 +++++++++++++ zetaclient/chains/ton/signer/signer_test.go | 233 +++++++++++++ .../chains/ton/signer/signer_tracker.go | 92 +++++ zetaclient/orchestrator/bootstrap.go | 57 +++- zetaclient/orchestrator/orchestrator.go | 117 ++++++- zetaclient/orchestrator/orchestrator_test.go | 83 +++++ zetaclient/testutils/mocks/chain_clients.go | 97 +++--- zetaclient/testutils/mocks/chain_signer.go | 107 +++--- zetaclient/testutils/mocks/ton_liteclient.go | 113 +++++- .../testutils/mocks/ton_signerliteclient.go | 118 +++++++ zetaclient/testutils/mocks/tss_signer.go | 25 ++ 54 files changed, 3272 insertions(+), 786 deletions(-) create mode 100644 e2e/e2etests/test_ton_withdrawal.go delete mode 100644 e2e/runner/ton/gateway.compiled.json rename {e2e/runner => pkg/contracts}/ton/coin.go (77%) create mode 100644 pkg/contracts/ton/gateway.compiled.json create mode 100644 pkg/contracts/ton/gateway_deploy.go create mode 100644 pkg/contracts/ton/gateway_msg.go delete mode 100644 pkg/contracts/ton/gateway_op.go create mode 100644 pkg/contracts/ton/gateway_parse.go create mode 100644 pkg/contracts/ton/testdata/06-withdrawal.json rename testutil/sample/{sample_ton.go => ton.go} (71%) rename testutil/sample/{sample_ton_test.go => ton_test.go} (100%) create mode 100644 zetaclient/chains/ton/observer/outbound.go create mode 100644 zetaclient/chains/ton/observer/outbound_test.go create mode 100644 zetaclient/chains/ton/signer/signer.go create mode 100644 zetaclient/chains/ton/signer/signer_test.go create mode 100644 zetaclient/chains/ton/signer/signer_tracker.go create mode 100644 zetaclient/testutils/mocks/ton_signerliteclient.go diff --git a/changelog.md b/changelog.md index 1d5ceb9114..4e58815e99 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,7 @@ * [3025](https://github.com/zeta-chain/node/pull/3025) - standard memo for Bitcoin inbound * [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater * [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile +* [3020](https://github.com/zeta-chain/node/pull/3020) - add support for TON withdrawals ### Refactor diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index dca027f3c8..7cde1a92ad 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -418,6 +418,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { tonTests := []string{ e2etests.TestTONDepositName, e2etests.TestTONDepositAndCallName, + e2etests.TestTONWithdrawName, } eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...)) diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 363f5e1f6d..eb6453052f 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -254,6 +254,8 @@ services: entrypoint: ["/usr/bin/start-solana.sh"] ton: + # figure out why E2E fail with MyLocalTon v124 @ deposit: deployer.CreateWallet(..) + # image: ghcr.io/zeta-chain/ton-docker:4f08c1d image: ghcr.io/zeta-chain/ton-docker:a69ea0f container_name: ton hostname: ton diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 713ad935ff..30c25b33a3 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -67,6 +67,7 @@ const ( */ TestTONDepositName = "ton_deposit" TestTONDepositAndCallName = "ton_deposit_and_call" + TestTONWithdrawName = "ton_withdraw" /* Bitcoin tests @@ -471,6 +472,14 @@ var AllE2ETests = []runner.E2ETest{ }, TestTONDepositAndCall, ), + runner.NewE2ETest( + TestTONWithdrawName, + "withdraw TON from ZEVM", + []runner.ArgDefinition{ + {Description: "amount in nano tons", DefaultValue: "2000000000"}, // 2.0 TON + }, + TestTONWithdraw, + ), /* Bitcoin tests */ diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go index 73860629df..4ee6b4a6fb 100644 --- a/e2e/e2etests/test_ton_deposit.go +++ b/e2e/e2etests/test_ton_deposit.go @@ -1,53 +1,40 @@ package e2etests import ( - "time" - - "cosmossdk.io/math" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/e2e/runner" - "github.com/zeta-chain/node/e2e/runner/ton" - "github.com/zeta-chain/node/pkg/chains" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" "github.com/zeta-chain/node/testutil/sample" - cctypes "github.com/zeta-chain/node/x/crosschain/types" ) func TestTONDeposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) // Given deployer - ctx, deployer, chain := r.Ctx, r.TONDeployer, chains.TONLocalnet + ctx, deployer := r.Ctx, r.TONDeployer // Given amount amount := parseUint(r, args[0]) - // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc#L28 - // (will be optimized & dynamic in the future) - depositFee := math.NewUint(10_000_000) + // Given approx deposit fee + depositFee, err := r.TONGateway.GetTxFee(ctx, r.Clients.TON, toncontracts.OpDeposit) + require.NoError(r, err) // Given sample wallet with a balance of 50 TON - sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50)) + sender, err := deployer.CreateWallet(ctx, toncontracts.Coins(50)) require.NoError(r, err) // Given sample EVM address recipient := sample.EthAddress() // ACT - err = r.TONDeposit(sender, amount, recipient) + cctx, err := r.TONDeposit(sender, amount, recipient) // ASSERT require.NoError(r, err) - // Wait for CCTX mining - filter := func(cctx *cctypes.CrossChainTx) bool { - return cctx.InboundParams.SenderChainId == chain.ChainId && - cctx.InboundParams.Sender == sender.GetAddress().ToRaw() - } - - cctx := r.WaitForSpecificCCTX(filter, time.Minute) - // Check CCTX expectedDeposit := amount.Sub(depositFee) diff --git a/e2e/e2etests/test_ton_deposit_and_call.go b/e2e/e2etests/test_ton_deposit_and_call.go index 43e5dcc4e0..d0b2cac9e0 100644 --- a/e2e/e2etests/test_ton_deposit_and_call.go +++ b/e2e/e2etests/test_ton_deposit_and_call.go @@ -1,35 +1,30 @@ package e2etests import ( - "time" - - "cosmossdk.io/math" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/e2e/runner" - "github.com/zeta-chain/node/e2e/runner/ton" "github.com/zeta-chain/node/e2e/utils" - "github.com/zeta-chain/node/pkg/chains" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" testcontract "github.com/zeta-chain/node/testutil/contracts" - cctypes "github.com/zeta-chain/node/x/crosschain/types" ) func TestTONDepositAndCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) // Given deployer - ctx, deployer, chain := r.Ctx, r.TONDeployer, chains.TONLocalnet + ctx, deployer := r.Ctx, r.TONDeployer // Given amount amount := parseUint(r, args[0]) - // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc#L28 - // (will be optimized & dynamic in the future) - depositFee := math.NewUint(10_000_000) + // Given approx depositAndCall fee + depositFee, err := r.TONGateway.GetTxFee(ctx, r.Clients.TON, toncontracts.OpDepositAndCall) + require.NoError(r, err) // Given sample wallet with a balance of 50 TON - sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50)) + sender, err := deployer.CreateWallet(ctx, toncontracts.Coins(50)) require.NoError(r, err) // Given sample zEVM contract @@ -41,19 +36,11 @@ func TestTONDepositAndCall(r *runner.E2ERunner, args []string) { callData := []byte("hello from TON!") // ACT - err = r.TONDepositAndCall(sender, amount, contractAddr, callData) + _, err = r.TONDepositAndCall(sender, amount, contractAddr, callData) // ASSERT require.NoError(r, err) - // Wait for CCTX mining - filter := func(cctx *cctypes.CrossChainTx) bool { - return cctx.InboundParams.SenderChainId == chain.ChainId && - cctx.InboundParams.Sender == sender.GetAddress().ToRaw() - } - - r.WaitForSpecificCCTX(filter, time.Minute) - expectedDeposit := amount.Sub(depositFee) // check if example contract has been called, bar value should be set to amount diff --git a/e2e/e2etests/test_ton_withdrawal.go b/e2e/e2etests/test_ton_withdrawal.go new file mode 100644 index 0000000000..d78f624716 --- /dev/null +++ b/e2e/e2etests/test_ton_withdrawal.go @@ -0,0 +1,93 @@ +package e2etests + +import ( + "math/big" + + "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" +) + +// TODO: Add "withdraw_many_concurrent" test +// https://github.com/zeta-chain/node/issues/3044 + +func TestTONWithdraw(r *runner.E2ERunner, args []string) { + // ARRANGE + require.Len(r, args, 1) + + // Given a deployer + _, deployer := r.Ctx, r.TONDeployer + + // And zEVM sender + zevmSender := r.ZEVMAuth.From + + // Given his ZRC-20 balance + senderZRC20BalanceBefore, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, zevmSender) + require.NoError(r, err) + r.Logger.Info("zEVM sender's ZRC20 TON balance before withdraw: %d", senderZRC20BalanceBefore) + + // Given another TON wallet + tonRecipient, err := deployer.CreateWallet(r.Ctx, toncontracts.Coins(1)) + require.NoError(r, err) + + tonRecipientBalanceBefore, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress()) + require.NoError(r, err) + + r.Logger.Info("Recipient's TON balance before withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceBefore)) + + // Given amount to withdraw (and approved amount in TON ZRC20 to cover the gas fee) + amount := parseUint(r, args[0]) + approvedAmount := amount.Add(toncontracts.Coins(1)) + + // ACT + cctx := r.WithdrawTONZRC20(tonRecipient.GetAddress(), amount.BigInt(), approvedAmount.BigInt()) + + // ASSERT + r.Logger.Info( + "Withdraw TON ZRC20 transaction (with %s) sent: %+v", + toncontracts.FormatCoins(amount), + map[string]any{ + "zevm_sender": zevmSender.Hex(), + "ton_recipient": tonRecipient.GetAddress().ToRaw(), + "ton_amount": toncontracts.FormatCoins(amount), + "cctx_index": cctx.Index, + "ton_hash": cctx.GetCurrentOutboundParam().Hash, + "zevm_hash": cctx.InboundParams.ObservedHash, + }, + ) + + // Make sure that recipient's TON balance has increased + tonRecipientBalanceAfter, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress()) + require.NoError(r, err) + + r.Logger.Info("Recipient's balance after withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceAfter)) + + // Make sure that sender's ZRC20 balance has decreased + senderZRC20BalanceAfter, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, zevmSender) + require.NoError(r, err) + r.Logger.Info("zEVM sender's ZRC20 TON balance after withdraw: %d", senderZRC20BalanceAfter) + r.Logger.Info( + "zEVM sender's ZRC20 TON balance diff: %d", + big.NewInt(0).Sub(senderZRC20BalanceBefore, senderZRC20BalanceAfter), + ) + + // Make sure that TON withdrawal CCTX contain outgoing message with exact withdrawal amount + lt, hash, err := liteapi.TransactionHashFromString(cctx.GetCurrentOutboundParam().Hash) + require.NoError(r, err) + + txs, err := r.Clients.TON.GetTransactions(r.Ctx, 1, r.TONGateway.AccountID(), lt, hash) + require.NoError(r, err) + require.Len(r, txs, 1) + + // TON coins that were withdrawn from GW to the recipient + inMsgAmount := math.NewUint( + uint64(txs[0].Msgs.OutMsgs.Values()[0].Value.Info.IntMsgInfo.Value.Grams), + ) + + // #nosec G115 always in range + require.Equal(r, int(amount.Uint64()), int(inMsgAmount.Uint64())) +} diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go index 62d89c3329..49feb5d95a 100644 --- a/e2e/runner/setup_ton.go +++ b/e2e/runner/setup_ton.go @@ -36,15 +36,13 @@ func (r *E2ERunner) SetupTON() error { depAddr := deployer.GetAddress() r.Logger.Print("💎TON Deployer %s (%s)", depAddr.ToRaw(), depAddr.ToHuman(false, true)) - gwAccount, err := ton.ConstructGatewayAccount(r.TSSAddress) + // 2. Deploy Gateway + gwAccount, err := ton.ConstructGatewayAccount(depAddr, r.TSSAddress) if err != nil { return errors.Wrap(err, "unable to initialize TON gateway") } - // 2. Deploy Gateway - initStateAmount := ton.TONCoins(10) - - if err := deployer.Deploy(ctx, gwAccount, initStateAmount); err != nil { + if err = deployer.Deploy(ctx, gwAccount, toncontracts.Coins(1)); err != nil { return errors.Wrapf(err, "unable to deploy TON gateway") } @@ -65,8 +63,18 @@ func (r *E2ERunner) SetupTON() error { return fmt.Errorf("TON gateway balance is zero") } + // 4. Deposit 100 TON deployer to Zevm Auth + gw := toncontracts.NewGateway(gwAccount.ID) + veryFirstDeposit := toncontracts.Coins(1000) + zevmRecipient := r.ZEVMAuth.From + + err = gw.SendDeposit(ctx, &deployer.Wallet, veryFirstDeposit, zevmRecipient, 0) + if err != nil { + return errors.Wrap(err, "unable to send deposit to TON gateway") + } + r.TONDeployer = deployer - r.TONGateway = toncontracts.NewGateway(gwAccount.ID) + r.TONGateway = gw return r.ensureTONChainParams(gwAccount) } @@ -103,6 +111,11 @@ func (r *E2ERunner) ensureTONChainParams(gw *ton.AccountInit) error { return errors.Wrap(err, "unable to broadcast TON chain params tx") } + resetMsg := observertypes.NewMsgResetChainNonces(creator, chainID, 0, 0) + if _, err := r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, resetMsg); err != nil { + return errors.Wrap(err, "unable to broadcast TON chain nonce reset tx") + } + r.Logger.Print("💎Voted for adding TON chain params (localnet). Waiting for confirmation") query := &observertypes.QueryGetChainParamsForChainRequest{ChainId: chainID} diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go index 8746e25977..eb8e5346ac 100644 --- a/e2e/runner/ton.go +++ b/e2e/runner/ton.go @@ -1,12 +1,20 @@ package runner import ( + "math/big" + "time" + "cosmossdk.io/math" eth "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/ton" "github.com/tonkeeper/tongo/wallet" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/chains" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + cctypes "github.com/zeta-chain/node/x/crosschain/types" ) // we need to use this send mode due to how wallet V5 works @@ -16,7 +24,13 @@ import ( const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors // TONDeposit deposit TON to Gateway contract -func (r *E2ERunner) TONDeposit(sender *wallet.Wallet, amount math.Uint, zevmRecipient eth.Address) error { +func (r *E2ERunner) TONDeposit( + sender *wallet.Wallet, + amount math.Uint, + zevmRecipient eth.Address, +) (*cctypes.CrossChainTx, error) { + chain := chains.TONLocalnet + require.NotNil(r, r.TONGateway, "TON Gateway is not initialized") require.NotNil(r, sender, "Sender wallet is nil") @@ -30,7 +44,21 @@ func (r *E2ERunner) TONDeposit(sender *wallet.Wallet, amount math.Uint, zevmReci zevmRecipient.Hex(), ) - return r.TONGateway.SendDeposit(r.Ctx, sender, amount, zevmRecipient, tonDepositSendCode) + // Send TX + err := r.TONGateway.SendDeposit(r.Ctx, sender, amount, zevmRecipient, tonDepositSendCode) + if err != nil { + return nil, errors.Wrap(err, "failed to send TON deposit") + } + + filter := func(cctx *cctypes.CrossChainTx) bool { + return cctx.InboundParams.SenderChainId == chain.ChainId && + cctx.InboundParams.Sender == sender.GetAddress().ToRaw() + } + + // Wait for cctx + cctx := r.WaitForSpecificCCTX(filter, time.Minute) + + return cctx, nil } // TONDepositAndCall deposit TON to Gateway contract with call data. @@ -39,7 +67,9 @@ func (r *E2ERunner) TONDepositAndCall( amount math.Uint, zevmRecipient eth.Address, callData []byte, -) error { +) (*cctypes.CrossChainTx, error) { + chain := chains.TONLocalnet + require.NotNil(r, r.TONGateway, "TON Gateway is not initialized") require.NotNil(r, sender, "Sender wallet is nil") @@ -55,5 +85,43 @@ func (r *E2ERunner) TONDepositAndCall( string(callData), ) - return r.TONGateway.SendDepositAndCall(r.Ctx, sender, amount, zevmRecipient, callData, tonDepositSendCode) + err := r.TONGateway.SendDepositAndCall(r.Ctx, sender, amount, zevmRecipient, callData, tonDepositSendCode) + if err != nil { + return nil, errors.Wrap(err, "failed to send TON deposit and call") + } + + filter := func(cctx *cctypes.CrossChainTx) bool { + return cctx.InboundParams.SenderChainId == chain.ChainId && + cctx.InboundParams.Sender == sender.GetAddress().ToRaw() + } + + // Wait for cctx + cctx := r.WaitForSpecificCCTX(filter, time.Minute) + + return cctx, nil +} + +// WithdrawTONZRC20 withdraws an amount of ZRC20 TON tokens +func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveAmount *big.Int) *cctypes.CrossChainTx { + // approve + tx, err := r.TONZRC20.Approve(r.ZEVMAuth, r.TONZRC20Addr, approveAmount) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "approve") + + // withdraw + tx, err = r.TONZRC20.Withdraw(r.ZEVMAuth, []byte(to.ToRaw()), amount) + require.NoError(r, err) + r.Logger.EVMTransaction(*tx, "withdraw") + + // wait for tx receipt + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "withdraw") + r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + utils.RequireCCTXStatus(r, cctx, cctypes.CctxStatus_OutboundMined) + + return cctx } diff --git a/e2e/runner/ton/accounts.go b/e2e/runner/ton/accounts.go index 3ffd8c0907..bed0ba7c21 100644 --- a/e2e/runner/ton/accounts.go +++ b/e2e/runner/ton/accounts.go @@ -1,8 +1,6 @@ package ton import ( - _ "embed" - "encoding/json" "fmt" eth "github.com/ethereum/go-ethereum/common" @@ -18,12 +16,6 @@ import ( const workchainID = 0 -// https://github.com/zeta-chain/protocol-contracts-ton -// `make compile` -// -//go:embed gateway.compiled.json -var tonGatewayCodeJSON []byte - type AccountInit struct { Code *boc.Cell Data *boc.Cell @@ -90,69 +82,15 @@ func ConstructAccount(code, data *boc.Cell) (*AccountInit, error) { }, nil } -func ConstructGatewayAccount(tss eth.Address) (*AccountInit, error) { - code, err := getGatewayCode() - if err != nil { - return nil, errors.Wrap(err, "unable to get TON Gateway code") - } - - data, err := buildGatewayData(tss) - if err != nil { - return nil, errors.Wrap(err, "unable to build TON Gateway data") - } - - return ConstructAccount(code, data) -} - -func getGatewayCode() (*boc.Cell, error) { - var code struct { - Hex string `json:"hex"` - } - - if err := json.Unmarshal(tonGatewayCodeJSON, &code); err != nil { - return nil, errors.Wrap(err, "unable to unmarshal TON Gateway code") - } - - cells, err := boc.DeserializeBocHex(code.Hex) - if err != nil { - return nil, errors.Wrap(err, "unable to deserialize TON Gateway code") - } - - if len(cells) != 1 { - return nil, errors.New("invalid cells count") - } - - return cells[0], nil -} - -// buildGatewayState returns TON Gateway initial state data cell -func buildGatewayData(tss eth.Address) (*boc.Cell, error) { - const evmAddressBits = 20 * 8 - - tssSlice := boc.NewBitString(evmAddressBits) - if err := tssSlice.WriteBytes(tss.Bytes()); err != nil { - return nil, errors.Wrap(err, "unable to convert TSS address to ton slice") - } - - var ( - zeroCoins = tlb.Coins(0) - enc = &tlb.Encoder{} - cell = boc.NewCell() - ) - - err := toncontracts.ErrCollect( - cell.WriteBit(true), // deposits_enabled - zeroCoins.MarshalTLB(cell, enc), // total_locked - zeroCoins.MarshalTLB(cell, enc), // fees - cell.WriteUint(0, 32), // seqno - cell.WriteBitString(tssSlice), // tss_address +// ConstructGatewayAccount constructs gateway AccountInit. +// - Authority is the address of the gateway "admin". +// - TSS is the EVM address of TSS. +// - Deposits are enabled by default. +func ConstructGatewayAccount(authority ton.AccountID, tss eth.Address) (*AccountInit, error) { + return ConstructAccount( + toncontracts.GatewayCode(), + toncontracts.GatewayStateInit(authority, tss, true), ) - - if err != nil { - return nil, errors.Wrap(err, "unable to write TON Gateway state cell") - } - - return cell, nil } // copied from tongo wallets_common.go diff --git a/e2e/runner/ton/accounts_test.go b/e2e/runner/ton/accounts_test.go index 50ec07ee79..df1668c3af 100644 --- a/e2e/runner/ton/accounts_test.go +++ b/e2e/runner/ton/accounts_test.go @@ -1,57 +1,12 @@ package ton import ( - "crypto/ecdsa" - "encoding/hex" "testing" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "github.com/tonkeeper/tongo/wallet" ) -func TestGateway(t *testing.T) { - // ARRANGE - // Given TSS address - const sampleTSSPrivateKey = "0xb984cd65727cfd03081fc7bf33bf5c208bca697ce16139b5ded275887e81395a" - - pkBytes, err := hex.DecodeString(sampleTSSPrivateKey[2:]) - require.NoError(t, err) - - privateKey, err := crypto.ToECDSA(pkBytes) - require.NoError(t, err) - - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - require.True(t, ok) - - tss := crypto.PubkeyToAddress(*publicKeyECDSA) - - t.Run("Build gateway deployment payload", func(t *testing.T) { - // ACT - codeCell, errCode := getGatewayCode() - stateCell, errState := buildGatewayData(tss) - - // ASSERT - require.NoError(t, errCode) - require.NoError(t, errState) - - codeString, err := codeCell.ToBocStringCustom(false, true, false, 0) - require.NoError(t, err) - - stateString, err := stateCell.ToBocStringCustom(false, true, false, 0) - require.NoError(t, err) - - t.Logf("Gateway code: %s", codeString) - t.Logf("Gateway state: %s", stateString) - - // Taken from jest tests in protocol-contracts-ton (using the same TSS address private key) - const expectedState = "b5ee9c7241010101001c0000338000000000124d38a790fdf1d9311fae87d4b21aeffd77bc26c0776433f3" - - require.Equal(t, expectedState, stateString) - }) -} - func TestWalletConstruction(t *testing.T) { // ARRANGE seed := wallet.RandomSeed() diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go index 858cc8af79..4735d3be10 100644 --- a/e2e/runner/ton/deployer.go +++ b/e2e/runner/ton/deployer.go @@ -10,6 +10,8 @@ import ( "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" "github.com/tonkeeper/tongo/wallet" + + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" ) // Deployer represents a wrapper around ton Wallet with some helpful methods. @@ -74,7 +76,7 @@ func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID) (math.Uin // Fund sends the given amount of coins to the recipient. Returns tx hash and error. func (d *Deployer) Fund(ctx context.Context, recipient ton.AccountID, amount math.Uint) (ton.Bits256, error) { msg := wallet.SimpleTransfer{ - Amount: UintToCoins(amount), + Amount: toncontracts.UintToCoins(amount), Address: recipient, } @@ -84,7 +86,7 @@ func (d *Deployer) Fund(ctx context.Context, recipient ton.AccountID, amount mat // Deploy deploys AccountInit with the given amount of coins. Returns tx hash and error. func (d *Deployer) Deploy(ctx context.Context, account *AccountInit, amount math.Uint) error { msg := wallet.Message{ - Amount: UintToCoins(amount), + Amount: toncontracts.UintToCoins(amount), Address: account.ID, Code: account.Code, Data: account.Data, diff --git a/e2e/runner/ton/gateway.compiled.json b/e2e/runner/ton/gateway.compiled.json deleted file mode 100644 index 5177dd0d6c..0000000000 --- a/e2e/runner/ton/gateway.compiled.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "hash": "a675833643f352d5d40e08efc5f093b6a0e6c246531ff1ba44374a30d8366d18", - "hashBase64": "pnWDNkPzUtXUDgjvxfCTtqDmwkZTH/G6RDdKMNg2bRg=", - "hex": "b5ee9c724102130100039b000114ff00f4a413f4bcf2c80b01020120020b020148030802b8d033d0d303fa40300171b0f2d06420fa4430c000f2e06922d749c160f2d06502d31fd33f593020c064925f04e020c065e302c0668ea0218208989680b9f2d06a20d7498100a0b9f2d067d39f20c702f2d068d430db3ce05f03f2c066040503ec30218208989680b9f2d06a20d7498100a0b9f2d067d39f30db3cdb3c018208989680a1f84221a0f862f8438208989680a0f863db3c708065c8cb1fcb3f5003cf1658fa02cb9fc98d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb000f061204f4db3cdb3c20830d8068218409a904a413f94130315301bc8e2f8d0858d95b1b081cda5e99481a5cc81d1bdbc8189a59ce8816d9dbdd0b081dd85b9d1760fe1430fe2030fe2030f2f0925f03e2028208989680a1f84221a0f862f8438208989680a0f863db3c708066c8cb1fcb3f5004cf165003fa0212cb9fccc90f061207000ef841c000f2d06e005e8d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb00020120090a010dbe64bed9e7c2240f0115bfda36d9e7c20fc217c22c0f04f8f2d31f218100c8ba8ff031db3cdb3cd0fa40fa00d31f3002fa4401c000f2e06921c000f2d06af844a413bdf2d06df80071c8c922103402318d04dcd95b9917dcda5b5c1b1957db595cdcd859d960fe1430708018c8cb05542005745003cb02cb07cbff58fa0212cb6ac901fb00f84201a1f862f844a4f864db3ce0210f10120c04528100c9ba8f9d31db3cdb3cd0d300d31f30f844a4bdf2d06df800f861f844a4f864db3ce0218100caba0f10120d047a8fb531db3cdb3cd0d69fd31f30f844a4bdf2d06df80020f865f844a4f8648bf6e65775f7473735f616464726573738fe1430fe2030db3ce0018100cbba0f10120e033a8f96db3cdb3cd0d4d31f30f844a4bdf2d06df800fb04db3ce030f2c0660f10120038ed44d0d30001f861fa0001f862fa0001f863d31f01f864d69f30f8650170810208d718d3ffd43020f90022bdf2d06bf84513db3c8d0558da1958dad7d958d91cd857dcda59db985d1d5c9960fe1430fe20c3fff2d06c11008e01d3070120c21e92a6e19720c21a92a6e5e0e201d3ffd3ff301034f912c3ff935f0471e002c304935f0372e0c8cbffcbff71f90403c8cbffc9d08100a0d722c705c0009173e07f0030f844f841c8cb00f842fa02f843fa02cb1ff845cf16c9ed54375cf328" -} \ No newline at end of file diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go index 665251eb39..0102e74517 100644 --- a/pkg/chains/chain.go +++ b/pkg/chains/chain.go @@ -81,6 +81,12 @@ func (chain Chain) EncodeAddress(b []byte) (string, error) { return "", err } return pk.String(), nil + case Consensus_catchain_consensus: + acc, err := ton.ParseAccountID(string(b)) + if err != nil { + return "", err + } + return acc.ToRaw(), nil default: return "", fmt.Errorf("chain id %d not supported", chain.ChainId) } diff --git a/e2e/runner/ton/coin.go b/pkg/contracts/ton/coin.go similarity index 77% rename from e2e/runner/ton/coin.go rename to pkg/contracts/ton/coin.go index dcd1c86009..10921facfc 100644 --- a/e2e/runner/ton/coin.go +++ b/pkg/contracts/ton/coin.go @@ -6,11 +6,12 @@ import ( "github.com/tonkeeper/tongo/utils" ) -// TONCoins takes amount in nano tons and returns it in tons. +// Coins takes amount in TON and returns it in nano tons. +// Example Coins(5) return math.Uint(5 * 10^9) nano tons. // //nolint:revive // in this context TON means 10^9 nano tons. //goland:noinspection GoNameStartsWithPackageName -func TONCoins(amount uint64) math.Uint { +func Coins(amount uint64) math.Uint { // 1 ton = 10^9 nano tons const mul = 1_000_000_000 diff --git a/pkg/contracts/ton/gateway.compiled.json b/pkg/contracts/ton/gateway.compiled.json new file mode 100644 index 0000000000..2f7d5444cb --- /dev/null +++ b/pkg/contracts/ton/gateway.compiled.json @@ -0,0 +1,5 @@ +{ + "hash": "7c82b775cb06df2b7b0329eac2db90cf6c9845425ad1ffe1f9fe11fa3eafcb4b", + "hashBase64": "fIK3dcsG3yt7AynqwtuQz2yYRUJa0f/h+f4R+j6vy0s=", + "hex": "b5ee9c7241021c010004d5000114ff00f4a413f4bcf2c80b010201200215020148030f0202cc040e0499d99e86981fd201800b8d8492f81f0107d22186000797034916ba4e0b079683281698fe99fac98106032492f8270106032f18110603347429836096d9e7040a7106d9e09dcf9683510c08064dd40506180903ee306c12ed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f865db3c20d749318100a0b9f2d067812710db3c5cbbf2d06a66a1f84221a0f862db3cc801fa0201fa02c98d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb0007181a04eced44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f865db3c20d7498100a0b9f2d067d39f3120c702f2d068d430830d8068218409a904a413f9406fa56fa1316c12c0009322f2f0de20c00002bcb192f2f09130e28132c8db3c5cbbf2d06a66a1f84221a0f862db3cc801fa0201fa02c907181a08000ef841c000f2d06e005e8d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb00046c8f2731ed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f86501db3cd30030f861db3ce0218100cabae302218100cbba0d1a0a0b024e31ed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f86501db3cd69f30f864db3c0d1a03688f2631ed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f86501db3cd430fb04db3ce0018100ccbae3025bf2c0660d1a0c025eed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f86501db3cfa403020fa4430c000f2e069f865db3c0d1a000ef845c705f2e06f0101b919020120101402015811120041b592fda89a1a60003f0c3f40003f0c5a63e03f0c7ad3e03f0c9f48061f0cbf0870047db72644180cb1d0c61024e21b679c04180cd1d0c61026591b679c041020193744302019575624302019775624302019975631d0c61029c41b679c10201917501818181301188e8581445cdb3ce0f2c0667018004dbfda376a268698000fc30fd0000fc31698f80fc31eb4f80fc327d20187c32fc20fc217c227c22c02d6f2ed44d0d30001f861fa0001f862d31f01f863d69f01f864fa4030f865f84401810208d71820c702f2d06bd43020f9004003db3c20c3ff8e218d0558da1958dad7d958d91cd857dcda59db985d1d5c9960fe1430fe2030f2c06c9130e2d0d31f018100c8bae30230f2c0661617008e01d3070120c21e92a6e19720c21a92a6e5e0e201d3ffd3ff301034f912c3ff935f0471e002c304935f0372e0c8cbffcbff71f90403c8cbffc9d08100a0d722c705c0009173e07f0396fa40fa00d31f3022fa4401c000f2e069f82814c705f2d07021c000f2d06af843bdf2d06d81445cdb3cf8425321a0b9f2d06af800f8425222a0a1f862f843a4f863db3cf80f71801001db3c181a1b014470c0ff948014f833948015f833e2d0db3c6c135db993135f03975aa101ab0fa8a0e2190058d307218100d1ba9c31d33fd33f5902f00c6c2113e0218100deba028100ddba12b196d33f01705202e07053000030f843f841c8cb00f842fa02cb1ff844cf16f845cf16c9ed5400668d04dcd95b9917dcda5b5c1b1957db595cdcd859d960fe14307002c8cb05542025745003cb02cb07cbff58fa02cb6ac901fb005bd61845" +} diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go index ab21a41ba3..31f1fe1d6d 100644 --- a/pkg/contracts/ton/gateway.go +++ b/pkg/contracts/ton/gateway.go @@ -2,6 +2,8 @@ package ton import ( + "context" + "cosmossdk.io/math" "github.com/pkg/errors" "github.com/tonkeeper/tongo/boc" @@ -24,9 +26,16 @@ type Gateway struct { accountID ton.AccountID } +type MethodRunner interface { + RunSmcMethod(ctx context.Context, acc ton.AccountID, method string, params tlb.VmStack) (uint32, tlb.VmStack, error) +} + +type Filter func(*Transaction) bool + const ( sizeOpCode = 32 sizeQueryID = 64 + sizeSeqno = 32 ) var ( @@ -47,31 +56,16 @@ func (gw *Gateway) AccountID() ton.AccountID { // ParseTransaction parses transaction to Transaction func (gw *Gateway) ParseTransaction(tx ton.Transaction) (*Transaction, error) { - if !tx.IsSuccess() { - exitCode := tx.Description.TransOrd.ComputePh.TrPhaseComputeVm.Vm.ExitCode - return nil, errors.Wrapf(ErrParse, "tx %s is not successful (exit code %d)", tx.Hash().Hex(), exitCode) - } - - if tx.Msgs.InMsg.Exists { - inbound, err := gw.parseInbound(tx) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse inbound tx %s", tx.Hash().Hex()) - } - - return inbound, nil - } - - outbound, err := gw.parseOutbound(tx) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse outbound tx %s", tx.Hash().Hex()) + if isOutbound(tx) { + return gw.parseOutbound(tx) } - return outbound, nil + return gw.parseInbound(tx) } // ParseAndFilter parses transaction and applies filter to it. Returns (tx, skip?, error) // If parse fails due to known error, skip is set to true -func (gw *Gateway) ParseAndFilter(tx ton.Transaction, filter func(*Transaction) bool) (*Transaction, bool, error) { +func (gw *Gateway) ParseAndFilter(tx ton.Transaction, filter Filter) (*Transaction, bool, error) { parsedTX, err := gw.ParseTransaction(tx) switch { case errors.Is(err, ErrParse): @@ -83,167 +77,31 @@ func (gw *Gateway) ParseAndFilter(tx ton.Transaction, filter func(*Transaction) } if !filter(parsedTX) { - return nil, true, nil + return parsedTX, true, nil } return parsedTX, false, nil } -// FilterInbounds filters transactions with deposit operations -func FilterInbounds(tx *Transaction) bool { return tx.IsInbound() } - -func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) { - body, err := parseInternalMessageBody(tx) - if err != nil { - return nil, errors.Wrap(err, "unable to parse body") - } - - intMsgInfo := tx.Msgs.InMsg.Value.Value.Info.IntMsgInfo - if intMsgInfo == nil { - return nil, errors.Wrap(ErrParse, "no internal message info") - } - - sourceID, err := ton.AccountIDFromTlb(intMsgInfo.Src) - if err != nil { - return nil, errors.Wrap(err, "unable to parse source account") - } - - destinationID, err := ton.AccountIDFromTlb(intMsgInfo.Dest) - if err != nil { - return nil, errors.Wrap(err, "unable to parse destination account") - } - - if gw.accountID != *destinationID { - return nil, errors.Wrap(ErrParse, "destination account is not gateway") - } - - op, err := body.ReadUint(sizeOpCode) - if err != nil { - return nil, errors.Wrap(err, "unable to read op code") - } - - var ( - sender = *sourceID - // #nosec G115 always in range - opCode = Op(op) - - content any - errContent error - ) - - switch opCode { - case OpDonate: - amount := intMsgInfo.Value.Grams - tx.TotalFees.Grams - content = Donation{Sender: sender, Amount: GramsToUint(amount)} - case OpDeposit: - content, errContent = parseDeposit(tx, sender, body) - case OpDepositAndCall: - content, errContent = parseDepositAndCall(tx, sender, body) - default: - // #nosec G115 always in range - return nil, errors.Wrapf(ErrUnknownOp, "op code %d", int64(op)) - } - - if errContent != nil { - // #nosec G115 always in range - return nil, errors.Wrapf(ErrParse, "unable to parse content for op code %d: %s", int64(op), errContent.Error()) - } - - return &Transaction{ - Transaction: tx, - Operation: opCode, - - content: content, - inbound: true, - }, nil -} - -func parseDeposit(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (Deposit, error) { - // skip query id - if err := body.Skip(sizeQueryID); err != nil { - return Deposit{}, err - } +// ParseAndFilterMany parses and filters many txs. +func (gw *Gateway) ParseAndFilterMany(txs []ton.Transaction, filter Filter) []*Transaction { + //goland:noinspection GoPreferNilSlice + out := []*Transaction{} - recipient, err := UnmarshalEVMAddress(body) - if err != nil { - return Deposit{}, errors.Wrap(err, "unable to read recipient") - } - - dl, err := parseDepositLog(tx) - if err != nil { - return Deposit{}, errors.Wrap(err, "unable to parse deposit log") - } - - return Deposit{ - Sender: sender, - Amount: dl.Amount, - Recipient: recipient, - }, nil -} - -type depositLog struct { - Amount math.Uint -} - -func parseDepositLog(tx ton.Transaction) (depositLog, error) { - messages := tx.Msgs.OutMsgs.Values() - if len(messages) == 0 { - return depositLog{}, errors.Wrap(ErrParse, "no out messages") - } - - // stored as ref - // cell log = begin_cell() - // .store_uint(op::internal::deposit, size::op_code_size) - // .store_uint(0, size::query_id_size) - // .store_slice(sender) - // .store_coins(deposit_amount) - // .store_uint(evm_recipient, size::evm_address) - // .end_cell(); - - var ( - bodyValue = boc.Cell(messages[0].Value.Body.Value) - body = &bodyValue - ) - - if err := body.Skip(sizeOpCode + sizeQueryID); err != nil { - return depositLog{}, errors.Wrap(err, "unable to skip bits") - } - - // skip msg address (ton sender) - if err := UnmarshalTLB(&tlb.MsgAddress{}, body); err != nil { - return depositLog{}, errors.Wrap(err, "unable to read sender address") - } - - var deposited tlb.Grams - if err := UnmarshalTLB(&deposited, body); err != nil { - return depositLog{}, errors.Wrap(err, "unable to read deposited amount") - } - - return depositLog{Amount: GramsToUint(deposited)}, nil -} - -func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (DepositAndCall, error) { - deposit, err := parseDeposit(tx, sender, body) - if err != nil { - return DepositAndCall{}, err - } - - callDataCell, err := body.NextRef() - if err != nil { - return DepositAndCall{}, errors.Wrap(err, "unable to read call data cell") - } + for i := range txs { + tx, skip, err := gw.ParseAndFilter(txs[i], filter) + if skip || err != nil { + continue + } - callData, err := UnmarshalSnakeCell(callDataCell) - if err != nil { - return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data") + out = append(out, tx) } - return DepositAndCall{Deposit: deposit, CallData: callData}, nil + return out } -func (gw *Gateway) parseOutbound(_ ton.Transaction) (*Transaction, error) { - return nil, errors.New("not implemented") -} +// FilterInbounds filters transactions with deposit operations +func FilterInbounds(tx *Transaction) bool { return tx.IsInbound() } func parseInternalMessageBody(tx ton.Transaction) (*boc.Cell, error) { if !tx.Msgs.InMsg.Exists { @@ -257,3 +115,33 @@ func parseInternalMessageBody(tx ton.Transaction) (*boc.Cell, error) { return &body, nil } + +var zero = math.NewUint(0) + +// GetTxFee returns maximum transaction fee for the given operation. +// Real fee may be lower. +func (gw *Gateway) GetTxFee(ctx context.Context, client MethodRunner, op Op) (math.Uint, error) { + const ( + method = "calculate_gas_fee" + sumType = "VmStkTinyInt" + ) + + query := tlb.VmStack{{SumType: sumType, VmStkTinyInt: int64(op)}} + + exitCode, res, err := client.RunSmcMethod(ctx, gw.accountID, method, query) + switch { + case err != nil: + return zero, err + case exitCode != 0: + return zero, errors.Errorf("calculate_gas_fee failed with exit code %d", exitCode) + case len(res) == 0: + return zero, errors.New("empty result") + case res[0].SumType != sumType: + return zero, errors.Errorf("res is not %s (got %s)", sumType, res[0].SumType) + case res[0].VmStkTinyInt <= 0: + return zero, errors.New("fee is zero or negative") + } + + // #nosec G115 positive + return math.NewUint(uint64(res[0].VmStkTinyInt)), nil +} diff --git a/pkg/contracts/ton/gateway_deploy.go b/pkg/contracts/ton/gateway_deploy.go new file mode 100644 index 0000000000..33ec6a422b --- /dev/null +++ b/pkg/contracts/ton/gateway_deploy.go @@ -0,0 +1,74 @@ +package ton + +import ( + _ "embed" + "encoding/json" + + eth "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" +) + +//go:embed gateway.compiled.json +var gatewayCode []byte + +// GatewayCode returns Gateway's code as cell +func GatewayCode() *boc.Cell { + c, err := getGatewayCode() + if err != nil { + panic(err) + } + + return c +} + +// GatewayStateInit returns Gateway's stateInit as cell +func GatewayStateInit(authority ton.AccountID, tss eth.Address, depositsEnabled bool) *boc.Cell { + c, err := buildGatewayStateInit(authority, tss, depositsEnabled) + if err != nil { + panic(err) + } + + return c +} + +func getGatewayCode() (*boc.Cell, error) { + var code struct { + Hex string `json:"hex"` + } + + if err := json.Unmarshal(gatewayCode, &code); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal TON Gateway code") + } + + cells, err := boc.DeserializeBocHex(code.Hex) + if err != nil { + return nil, errors.Wrap(err, "unable to deserialize TON Gateway code") + } + + if len(cells) != 1 { + return nil, errors.New("invalid cells count") + } + + return cells[0], nil +} + +func buildGatewayStateInit(authority ton.AccountID, tss eth.Address, depositsEnabled bool) (*boc.Cell, error) { + cell := boc.NewCell() + + err := ErrCollect( + cell.WriteBit(depositsEnabled), // deposits_enabled + tlb.Marshal(cell, tlb.Coins(0)), // total_locked + cell.WriteUint(0, 32), // seqno + cell.WriteBytes(tss.Bytes()), // tss_address + tlb.Marshal(cell, authority.ToMsgAddress()), // authority_address (TON) + ) + + if err != nil { + return nil, errors.Wrap(err, "unable to write TON Gateway state cell") + } + + return cell, nil +} diff --git a/pkg/contracts/ton/gateway_msg.go b/pkg/contracts/ton/gateway_msg.go new file mode 100644 index 0000000000..543f7d5a22 --- /dev/null +++ b/pkg/contracts/ton/gateway_msg.go @@ -0,0 +1,215 @@ +package ton + +import ( + "errors" + + "cosmossdk.io/math" + eth "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" +) + +// Op operation code +type Op uint32 + +// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc +// Inbound operations +const ( + OpDonate Op = 100 + iota + OpDeposit + OpDepositAndCall +) + +const OpWithdraw Op = 200 + +// Donation represents a donation operation +type Donation struct { + Sender ton.AccountID + Amount math.Uint +} + +// AsBody casts struct as internal message body. +func (d Donation) AsBody() (*boc.Cell, error) { + b := boc.NewCell() + err := ErrCollect( + b.WriteUint(uint64(OpDonate), sizeOpCode), + b.WriteUint(0, sizeQueryID), + ) + + return b, err +} + +// Deposit represents a deposit operation +type Deposit struct { + Sender ton.AccountID + Amount math.Uint + Recipient eth.Address +} + +// Memo casts deposit to memo bytes +func (d Deposit) Memo() []byte { + return d.Recipient.Bytes() +} + +// AsBody casts struct as internal message body. +func (d Deposit) AsBody() (*boc.Cell, error) { + b := boc.NewCell() + + return b, writeDepositBody(b, d.Recipient) +} + +// DepositAndCall represents a deposit and call operation +type DepositAndCall struct { + Deposit + CallData []byte +} + +// Memo casts deposit to call to memo bytes +func (d DepositAndCall) Memo() []byte { + recipient := d.Recipient.Bytes() + out := make([]byte, 0, len(recipient)+len(d.CallData)) + + out = append(out, recipient...) + out = append(out, d.CallData...) + + return out +} + +// AsBody casts struct to internal message body. +func (d DepositAndCall) AsBody() (*boc.Cell, error) { + b := boc.NewCell() + + return b, writeDepositAndCallBody(b, d.Recipient, d.CallData) +} + +func writeDepositBody(b *boc.Cell, recipient eth.Address) error { + return ErrCollect( + b.WriteUint(uint64(OpDeposit), sizeOpCode), + b.WriteUint(0, sizeQueryID), + b.WriteBytes(recipient.Bytes()), + ) +} + +func writeDepositAndCallBody(b *boc.Cell, recipient eth.Address, callData []byte) error { + if len(callData) == 0 { + return errors.New("call data is empty") + } + + callDataCell, err := MarshalSnakeCell(callData) + if err != nil { + return err + } + + return ErrCollect( + b.WriteUint(uint64(OpDepositAndCall), sizeOpCode), + b.WriteUint(0, sizeQueryID), + b.WriteBytes(recipient.Bytes()), + b.AddRef(callDataCell), + ) +} + +// Withdrawal represents a withdrawal external message +type Withdrawal struct { + Recipient ton.AccountID + Amount math.Uint + Seqno uint32 + Sig [65]byte +} + +func (w *Withdrawal) emptySig() bool { + return w.Sig == [65]byte{} +} + +// Hash returns hash of the withdrawal message. (used for signing) +func (w *Withdrawal) Hash() ([32]byte, error) { + payload, err := w.payload() + if err != nil { + return [32]byte{}, err + } + + return payload.Hash256() +} + +// SetSignature sets signature to the withdrawal message. +// Note that signature has the following order: [R, S, V (recovery ID)] +func (w *Withdrawal) SetSignature(sig [65]byte) { + copy(w.Sig[:], sig[:]) +} + +// Signer returns EVM address of the signer (e.g. TSS) +func (w *Withdrawal) Signer() (eth.Address, error) { + hash, err := w.Hash() + if err != nil { + return eth.Address{}, err + } + + var sig [65]byte + copy(sig[:], w.Sig[:]) + + // recovery id + // https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v + if sig[64] >= 27 { + sig[64] -= 27 + } + + pub, err := crypto.SigToPub(hash[:], sig[:]) + if err != nil { + return eth.Address{}, err + } + + return crypto.PubkeyToAddress(*pub), nil +} + +func (w *Withdrawal) AsBody() (*boc.Cell, error) { + payload, err := w.payload() + if err != nil { + return nil, err + } + + var ( + body = boc.NewCell() + v, r, s = splitSignature(w.Sig) + ) + + // note that in TVM, the order of signature is different (v, r, s) + err = ErrCollect( + body.WriteUint(uint64(v), 8), + body.WriteBytes(r[:]), + body.WriteBytes(s[:]), + body.AddRef(payload), + ) + if err != nil { + return nil, err + } + + return body, nil +} + +func (w *Withdrawal) payload() (*boc.Cell, error) { + payload := boc.NewCell() + + err := ErrCollect( + payload.WriteUint(uint64(OpWithdraw), sizeOpCode), + tlb.Marshal(payload, w.Recipient.ToMsgAddress()), + tlb.Marshal(payload, tlb.Coins(w.Amount.Uint64())), + payload.WriteUint(uint64(w.Seqno), sizeSeqno), + ) + + if err != nil { + return nil, errors.New("unable to marshal payload as cell") + } + + return payload, nil +} + +// Ton Virtual Machine (TVM) uses different order of signature params (v,r,s) instead of (r,s,v); +// Let's split them as required. +func splitSignature(sig [65]byte) (v byte, r [32]byte, s [32]byte) { + copy(r[:], sig[:32]) + copy(s[:], sig[32:64]) + v = sig[64] + + return v, r, s +} diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go deleted file mode 100644 index 7d711ab89c..0000000000 --- a/pkg/contracts/ton/gateway_op.go +++ /dev/null @@ -1,115 +0,0 @@ -package ton - -import ( - "errors" - - "cosmossdk.io/math" - eth "github.com/ethereum/go-ethereum/common" - "github.com/tonkeeper/tongo/boc" - "github.com/tonkeeper/tongo/ton" -) - -// Op operation code -type Op uint32 - -// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc -// Inbound operations -const ( - OpDonate Op = 100 + iota - OpDeposit - OpDepositAndCall -) - -// Outbound operations -const ( - OpWithdraw Op = 200 + iota - SetDepositsEnabled - UpdateTSS - UpdateCode -) - -// Donation represents a donation operation -type Donation struct { - Sender ton.AccountID - Amount math.Uint -} - -// AsBody casts struct as internal message body. -func (d Donation) AsBody() (*boc.Cell, error) { - b := boc.NewCell() - err := ErrCollect( - b.WriteUint(uint64(OpDonate), sizeOpCode), - b.WriteUint(0, sizeQueryID), - ) - - return b, err -} - -// Deposit represents a deposit operation -type Deposit struct { - Sender ton.AccountID - Amount math.Uint - Recipient eth.Address -} - -// Memo casts deposit to memo bytes -func (d Deposit) Memo() []byte { - return d.Recipient.Bytes() -} - -// AsBody casts struct as internal message body. -func (d Deposit) AsBody() (*boc.Cell, error) { - b := boc.NewCell() - - return b, writeDepositBody(b, d.Recipient) -} - -// DepositAndCall represents a deposit and call operation -type DepositAndCall struct { - Deposit - CallData []byte -} - -// Memo casts deposit to call to memo bytes -func (d DepositAndCall) Memo() []byte { - recipient := d.Recipient.Bytes() - out := make([]byte, 0, len(recipient)+len(d.CallData)) - - out = append(out, recipient...) - out = append(out, d.CallData...) - - return out -} - -// AsBody casts struct to internal message body. -func (d DepositAndCall) AsBody() (*boc.Cell, error) { - b := boc.NewCell() - - return b, writeDepositAndCallBody(b, d.Recipient, d.CallData) -} - -func writeDepositBody(b *boc.Cell, recipient eth.Address) error { - return ErrCollect( - b.WriteUint(uint64(OpDeposit), sizeOpCode), - b.WriteUint(0, sizeQueryID), - b.WriteBytes(recipient.Bytes()), - ) -} - -func writeDepositAndCallBody(b *boc.Cell, recipient eth.Address, callData []byte) error { - if len(callData) == 0 { - return errors.New("call data is empty") - } - - callDataCell, err := MarshalSnakeCell(callData) - if err != nil { - return err - } - - return ErrCollect( - b.WriteUint(uint64(OpDepositAndCall), sizeOpCode), - b.WriteUint(0, sizeQueryID), - b.WriteBytes(recipient.Bytes()), - b.AddRef(callDataCell), - ) -} diff --git a/pkg/contracts/ton/gateway_parse.go b/pkg/contracts/ton/gateway_parse.go new file mode 100644 index 0000000000..4d6206f1ed --- /dev/null +++ b/pkg/contracts/ton/gateway_parse.go @@ -0,0 +1,293 @@ +package ton + +import ( + "cosmossdk.io/math" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" +) + +func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) { + body, err := parseInternalMessageBody(tx) + if err != nil { + return nil, errors.Wrap(err, "unable to parse body") + } + + intMsgInfo := tx.Msgs.InMsg.Value.Value.Info.IntMsgInfo + if intMsgInfo == nil { + return nil, errors.Wrap(ErrParse, "no internal message info") + } + + sourceID, err := ton.AccountIDFromTlb(intMsgInfo.Src) + if err != nil { + return nil, errors.Wrap(err, "unable to parse source account") + } + + destinationID, err := ton.AccountIDFromTlb(intMsgInfo.Dest) + if err != nil { + return nil, errors.Wrap(err, "unable to parse destination account") + } + + if gw.accountID != *destinationID { + return nil, errors.Wrap(ErrParse, "destination account is not gateway") + } + + op, err := body.ReadUint(sizeOpCode) + if err != nil { + return nil, errParse(err, "unable to read op code") + } + + var ( + // #nosec G115 always in range + opCode = Op(op) + sender = *sourceID + + content any + errContent error + ) + + switch opCode { + case OpDonate: + amount := intMsgInfo.Value.Grams - tx.TotalFees.Grams + content = Donation{Sender: sender, Amount: GramsToUint(amount)} + case OpDeposit: + content, errContent = parseDeposit(tx, sender, body) + case OpDepositAndCall: + content, errContent = parseDepositAndCall(tx, sender, body) + default: + // #nosec G115 always in range + return nil, errors.Wrapf(ErrUnknownOp, "op code %d", int64(op)) + } + + if errContent != nil { + // #nosec G115 always in range + return nil, errors.Wrapf(ErrParse, "unable to parse content for op code %d: %s", int64(op), errContent.Error()) + } + + return &Transaction{ + Transaction: tx, + Operation: opCode, + ExitCode: exitCodeFromTx(tx), + + content: content, + inbound: true, + }, nil +} + +func parseDeposit(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (Deposit, error) { + // skip query id + if err := body.Skip(sizeQueryID); err != nil { + return Deposit{}, err + } + + recipient, err := UnmarshalEVMAddress(body) + if err != nil { + return Deposit{}, errors.Wrap(err, "unable to read recipient") + } + + dl, err := parseDepositLog(tx) + if err != nil { + return Deposit{}, errors.Wrap(err, "unable to parse deposit log") + } + + return Deposit{ + Sender: sender, + Amount: dl.Amount, + Recipient: recipient, + }, nil +} + +type depositLog struct { + Amount math.Uint + DepositFee math.Uint +} + +func parseDepositLog(tx ton.Transaction) (depositLog, error) { + messages := tx.Msgs.OutMsgs.Values() + if len(messages) == 0 { + return depositLog{}, errors.Wrap(ErrParse, "no out messages") + } + + // stored as ref + // cell log = begin_cell() + // .store_coins(deposit_amount) + // .store_coins(tx_fee) + // .end_cell(); + + var ( + bodyValue = boc.Cell(messages[0].Value.Body.Value) + body = &bodyValue + ) + + var deposited tlb.Grams + if err := UnmarshalTLB(&deposited, body); err != nil { + return depositLog{}, errors.Wrap(err, "unable to read deposited amount") + } + + var depositFee tlb.Grams + if err := UnmarshalTLB(&depositFee, body); err != nil { + return depositLog{}, errors.Wrap(err, "unable to read deposit fee") + } + + return depositLog{ + Amount: GramsToUint(deposited), + DepositFee: GramsToUint(depositFee), + }, nil +} + +func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (DepositAndCall, error) { + deposit, err := parseDeposit(tx, sender, body) + if err != nil { + return DepositAndCall{}, err + } + + callDataCell, err := body.NextRef() + if err != nil { + return DepositAndCall{}, errors.Wrap(err, "unable to read call data cell") + } + + callData, err := UnmarshalSnakeCell(callDataCell) + if err != nil { + return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data") + } + + return DepositAndCall{Deposit: deposit, CallData: callData}, nil +} + +// an outbound is a tx that was initiated by TSS signature with external message +func isOutbound(tx ton.Transaction) bool { + return tx.Msgs.InMsg.Exists && + tx.Msgs.InMsg.Value.Value.Info.SumType == "ExtInMsgInfo" +} + +func (gw *Gateway) parseOutbound(tx ton.Transaction) (*Transaction, error) { + if !isOutbound(tx) { + return nil, errors.Wrap(ErrParse, "not an outbound transaction") + } + + extMsgBodyCell := boc.Cell(tx.Msgs.InMsg.Value.Value.Body.Value) + + sig, payload, err := parseExternalMessage(&extMsgBodyCell) + if err != nil { + return nil, errParse(err, "unable to parse external message") + } + + op, err := payload.ReadUint(sizeOpCode) + if err != nil { + return nil, errParse(err, "unable to read op code") + } + + // #nosec G115 always in range + opCode := Op(op) + + if opCode != OpWithdraw { + return nil, errors.Wrapf(ErrUnknownOp, "op code %d", op) + } + + withdrawal, err := parseWithdrawal(tx, sig, payload) + if err != nil { + return nil, errParse(err, "unable to parse withdrawal") + } + + return &Transaction{ + Transaction: tx, + Operation: opCode, + ExitCode: exitCodeFromTx(tx), + content: withdrawal, + }, nil +} + +// external message is essentially a cell with 65 bytes of ECDSA sig + cell_ref to payload +func parseExternalMessage(b *boc.Cell) ([65]byte, *boc.Cell, error) { + sig, err := b.ReadBytes(65) + if err != nil { + return [65]byte{}, nil, err + } + + var sigArray [65]byte + copy(sigArray[:], sig) + + payload, err := b.NextRef() + + return sigArray, payload, err +} + +func parseWithdrawal(tx ton.Transaction, sig [65]byte, payload *boc.Cell) (Withdrawal, error) { + // Note that ECDSA sig has the following order: (v, r, s) but in EVM we have (r, s, v) + var sigFlipped [65]byte + + copy(sigFlipped[:64], sig[1:]) + sigFlipped[64] = sig[0] + + var ( + recipient tlb.MsgAddress + amount tlb.Coins + seqno uint32 + ) + + err := ErrCollect( + tlb.Unmarshal(payload, &recipient), + tlb.Unmarshal(payload, &amount), + tlb.Unmarshal(payload, &seqno), + ) + if err != nil { + return Withdrawal{}, errors.Wrap(err, "unable to unmarshal payload") + } + + recipientAddr, err := parseAccount(recipient) + if err != nil { + return Withdrawal{}, errors.Wrap(err, "unable to parse recipient from payload") + } + + // ensure a single outgoing message for the withdrawal + if tx.OutMsgCnt != 1 { + return Withdrawal{}, errors.Wrap(ErrParse, "invalid out messages count") + } + + // tlb.Message{} + outMsg := tx.Msgs.OutMsgs.Values()[0].Value + if outMsg.Info.SumType != "IntMsgInfo" || outMsg.Info.IntMsgInfo == nil { + return Withdrawal{}, errors.Wrap(ErrParse, "invalid out message") + } + + msgRecipientAddr, err := parseAccount(outMsg.Info.IntMsgInfo.Dest) + + switch { + case err != nil: + return Withdrawal{}, errors.Wrap(err, "unable to parse recipient from out msg") + case recipientAddr != msgRecipientAddr: + // should not happen + return Withdrawal{}, errors.Wrap(ErrParse, "recipient mismatch") + case amount != outMsg.Info.IntMsgInfo.Value.Grams: + // should not happen + return Withdrawal{}, errors.Wrap(ErrParse, "amount mismatch") + } + + return Withdrawal{ + Recipient: recipientAddr, + Amount: math.NewUint(uint64(amount)), + Seqno: seqno, + Sig: sigFlipped, + }, nil +} + +func parseAccount(raw tlb.MsgAddress) (ton.AccountID, error) { + if raw.SumType != "AddrStd" { + return ton.AccountID{}, errors.Wrapf(ErrParse, "invalid address type %s", raw.SumType) + } + + return ton.AccountID{ + // #nosec G115 always in range + Workchain: int32(raw.AddrStd.WorkchainId), + Address: raw.AddrStd.Address, + }, nil +} + +func errParse(err error, msg string) error { + return errors.Wrapf(ErrParse, "%s (%s)", msg, err.Error()) +} + +func exitCodeFromTx(tx ton.Transaction) int32 { + return tx.Description.TransOrd.ComputePh.TrPhaseComputeVm.Vm.ExitCode +} diff --git a/pkg/contracts/ton/gateway_send.go b/pkg/contracts/ton/gateway_send.go index 5dd9c21340..a17bf9ef26 100644 --- a/pkg/contracts/ton/gateway_send.go +++ b/pkg/contracts/ton/gateway_send.go @@ -8,14 +8,25 @@ import ( "github.com/pkg/errors" "github.com/tonkeeper/tongo/boc" "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" "github.com/tonkeeper/tongo/wallet" ) -// Sender TON tx sender. +// Sender TON tx sender. Usually an interface to a wallet. type Sender interface { Send(ctx context.Context, messages ...wallet.Sendable) error } +// Client represents a sender that allows sending an arbitrary external message to the network. +type Client interface { + SendMessage(ctx context.Context, payload []byte) (uint32, error) +} + +type ExternalMsg interface { + emptySig() bool + AsBody() (*boc.Cell, error) +} + // see https://docs.ton.org/develop/smart-contracts/messages#message-modes const ( SendFlagSeparateFees = uint8(1) @@ -70,3 +81,38 @@ func (gw *Gateway) send(ctx context.Context, s Sender, amount math.Uint, body *b Mode: sendMode, }) } + +// SendExternalMessage sends an external message to the Gateway. +func (gw *Gateway) SendExternalMessage(ctx context.Context, s Client, msg ExternalMsg) (uint32, error) { + return sendExternalMessage(ctx, s, gw.accountID, msg) +} + +// inspired by tongo's wallet.Wallet{}.RawSendV2() +func sendExternalMessage(ctx context.Context, via Client, to ton.AccountID, msg ExternalMsg) (uint32, error) { + if msg.emptySig() { + return 0, errors.New("empty signature") + } + + body, err := msg.AsBody() + if err != nil { + return 0, errors.Wrap(err, "unable to encode msg as cell") + } + + extMsg, err := ton.CreateExternalMessage(to, body, nil, tlb.VarUInteger16{}) + if err != nil { + return 0, errors.Wrap(err, "unable to create external message") + } + + extMsgCell := boc.NewCell() + err = tlb.Marshal(extMsgCell, extMsg) + if err != nil { + return 0, errors.Wrap(err, "can not marshal wallet external message") + } + + payload, err := extMsgCell.ToBocCustom(false, false, false, 0) + if err != nil { + return 0, errors.Wrap(err, "can not serialize external message cell") + } + + return via.SendMessage(ctx, payload) +} diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go index dc680761ff..6698147e7c 100644 --- a/pkg/contracts/ton/gateway_test.go +++ b/pkg/contracts/ton/gateway_test.go @@ -1,12 +1,17 @@ package ton import ( + "crypto/ecdsa" "embed" + "encoding/hex" "encoding/json" "fmt" + "math/big" "strings" "testing" + "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/crypto" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,7 +21,8 @@ import ( ) func TestParsing(t *testing.T) { - swapBodyAndParse := func(gw *Gateway, tx ton.Transaction, body *boc.Cell) *Transaction { + // small helpers that allows to alter inMsg body and parse it again + alterBodyAndParse := func(gw *Gateway, tx ton.Transaction, body *boc.Cell) *Transaction { tx.Msgs.InMsg.Value.Value.Body.Value = tlb.Any(*body) parsed, err := gw.ParseTransaction(tx) @@ -44,7 +50,7 @@ func TestParsing(t *testing.T) { const ( expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f" - expectedDonation = 1_499_432_947 // 1.49... TON + expectedDonation = 599_509_877 // ~0.6 TON ) donation, err := parsedTX.Donation() @@ -54,7 +60,7 @@ func TestParsing(t *testing.T) { // Check that AsBody works var ( - parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(donation.AsBody())) + parsedTX2 = alterBodyAndParse(gw, tx, lo.Must(donation.AsBody())) donation2 = lo.Must(parsedTX2.Donation()) ) @@ -77,6 +83,7 @@ func TestParsing(t *testing.T) { // Check tx props assert.Equal(t, int(OpDeposit), int(parsedTX.Operation)) + assert.Zero(t, parsedTX.ExitCode) // Check deposit deposit, err := parsedTX.Deposit() @@ -84,13 +91,13 @@ func TestParsing(t *testing.T) { const ( expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f" - vitalikDotETH = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" - expectedDeposit = 990_000_000 // 0.99 TON + zevmRecipient = "0xA1eb8D65b765D259E7520B791bc4783AdeFDd998" + expectedDeposit = 996_000_000 // 0.996 TON ) assert.Equal(t, expectedSender, deposit.Sender.ToRaw()) assert.Equal(t, expectedDeposit, int(deposit.Amount.Uint64())) - assert.Equal(t, vitalikDotETH, deposit.Recipient.Hex()) + assert.Equal(t, zevmRecipient, deposit.Recipient.Hex()) // Check that other casting fails _, err = parsedTX.Donation() @@ -98,7 +105,7 @@ func TestParsing(t *testing.T) { // Check that AsBody works var ( - parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(deposit.AsBody())) + parsedTX2 = alterBodyAndParse(gw, tx, lo.Must(deposit.AsBody())) deposit2 = lo.Must(parsedTX2.Deposit()) ) @@ -127,25 +134,73 @@ func TestParsing(t *testing.T) { assert.NoError(t, err) const ( - expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f" - vitalikDotETH = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" - expectedDeposit = 490_000_000 // 0.49 TON + expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f" + zevmRecipient = "0xA1eb8D65b765D259E7520B791bc4783AdeFDd998" + expectedDeposit = 394_800_000 // 0.4 TON - tx fee + expectedCallData = `These "NFTs" - are they in the room with us right now?` ) - expectedCallData := readFixtureFile(t, "testdata/long-call-data.txt") - assert.Equal(t, expectedSender, depositAndCall.Sender.ToRaw()) assert.Equal(t, expectedDeposit, int(depositAndCall.Amount.Uint64())) - assert.Equal(t, vitalikDotETH, depositAndCall.Recipient.Hex()) - assert.Equal(t, expectedCallData, depositAndCall.CallData) + assert.Equal(t, zevmRecipient, depositAndCall.Recipient.Hex()) + assert.Equal(t, []byte(expectedCallData), depositAndCall.CallData) - // Check that AsBody works - var ( - parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(depositAndCall.AsBody())) - depositAndCall2 = lo.Must(parsedTX2.DepositAndCall()) - ) + t.Run("Long call data", func(t *testing.T) { + // ARRANGE + longCallData := readFixtureFile(t, "testdata/long-call-data.txt") + depositAndCall.CallData = longCallData + + // ACT + parsedTX = alterBodyAndParse(gw, tx, lo.Must(depositAndCall.AsBody())) + + depositAndCall2, err := parsedTX.DepositAndCall() + + // ASSERT + require.NoError(t, err) + + assert.Equal(t, longCallData, depositAndCall2.CallData) + }) + + }) + + t.Run("Withdrawal", func(t *testing.T) { + // ARRANGE + // Given a tx + tx, fx := getFixtureTX(t, "06-withdrawal") + + // Given a gateway contract + gw := NewGateway(ton.MustParseAccountID(fx.Account)) + + // ACT + parsedTX, err := gw.ParseTransaction(tx) + + // ASSERT + require.NoError(t, err) + assert.Equal(t, OpWithdraw, parsedTX.Operation) + assert.Zero(t, parsedTX.ExitCode) + + // 0.01 TON + const expectedAmount = 10_000_000 + + actualAmount := parsedTX.Msgs.OutMsgs.Values()[0].Value.Info.IntMsgInfo.Value.Grams + assert.Equal(t, tlb.Coins(expectedAmount), actualAmount) + + // Check withdrawal + w, err := parsedTX.Withdrawal() + require.NoError(t, err) + + expectedRecipient := ton.MustParseAccountID("0QDQ51yWafHKgOjMF4ZwOfGsxnQ2yx_InZQG1NGwMfs2y62E") + + assert.Equal(t, expectedRecipient, w.Recipient) + assert.Equal(t, uint32(1), w.Seqno) + assert.Equal(t, math.NewUint(expectedAmount), w.Amount) - assert.Equal(t, depositAndCall, depositAndCall2) + // Expect TSS signer address + signer, err := w.Signer() + require.NoError(t, err) + + const expectedTSS = "0xFA033cebd2EB4A800F74d70C10dfc8710fF0d148" + assert.Equal(t, expectedTSS, signer.Hex()) }) t.Run("Irrelevant tx", func(t *testing.T) { @@ -160,14 +215,14 @@ func TestParsing(t *testing.T) { // ACT _, err := gw.ParseTransaction(tx) - assert.ErrorIs(t, err, ErrParse) + assert.ErrorIs(t, err, ErrUnknownOp) // 102 is 'unknown op' // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/common/errors.fc - assert.ErrorContains(t, err, "is not successful (exit code 102)") + assert.ErrorContains(t, err, "unknown op") }) - t.Run("not a deposit nor withdrawal", func(t *testing.T) { + t.Run("not a deposit nor a withdrawal", func(t *testing.T) { // actually, it's a bounce of the previous tx // ARRANGE @@ -184,6 +239,72 @@ func TestParsing(t *testing.T) { }) } +func TestWithdrawal(t *testing.T) { + // FYI: asserts are checks with protocol-contracts-ton tests (Typescript lib) + + // ARRANGE + // Given a withdrawal msg + withdrawal := &Withdrawal{ + Recipient: ton.MustParseAccountID("0:552f6db5da0cae7f0b3ab4ab58d85927f6beb962cda426a6a6ee751c82cead1f"), + Amount: Coins(5), + Seqno: 2, + } + + const expectedHash = "e8eddf26276c747bd14d5161d18bc235c5c1a050187ab468996572d34e2f8f30" + + // ACT + hash, err := withdrawal.Hash() + + // ASSERT + require.NoError(t, err) + require.Equal(t, expectedHash, fmt.Sprintf("%x", hash[:])) + + t.Run("Sign", func(t *testing.T) { + // ARRANGE + // Given a sample EVM wallet (simulates TSS) + privateKey := evmWallet(t, "0xb984cd65727cfd03081fc7bf33bf5c208bca697ce16139b5ded275887e81395a") + + // Given ECDSA signature ... + sig, err := crypto.Sign(hash[:], privateKey) + require.NoError(t, err) + + var sigArray [65]byte + copy(sigArray[:], sig) + + // That is set in withdrawal + withdrawal.SetSignature(sigArray) + + // ACT + // Convert withdrawal to external-message's body + body, err := withdrawal.AsBody() + require.NoError(t, err) + + // ASSERT + // Check that sig is not empty + require.False(t, withdrawal.emptySig()) + + // Ensure that signature has the right format when decoding + var ( + r = big.NewInt(0).SetBytes(sig[:32]) + s = big.NewInt(0).SetBytes(sig[32:64]) + v = sig[64] + ) + + actualV, err := body.ReadUint(8) + require.NoError(t, err) + + actualR, err := body.ReadBigUint(256) + require.NoError(t, err) + + actualS, err := body.ReadBigUint(256) + require.NoError(t, err) + + assert.Equal(t, 0, r.Cmp(actualR)) + assert.Equal(t, 0, s.Cmp(actualS)) + assert.Equal(t, uint64(v), actualV) + }) +} + func TestFiltering(t *testing.T) { t.Run("Inbound", func(t *testing.T) { for _, tt := range []struct { @@ -237,8 +358,8 @@ func TestFixtures(t *testing.T) { tx, _ := getFixtureTX(t, "01-deposit") // ASSERT - require.Equal(t, uint64(26023788000003), tx.Lt) - require.Equal(t, "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf", tx.Hash().Hex()) + require.Equal(t, uint64(27040013000003), tx.Lt) + require.Equal(t, "f893d7ed7fc3d73aedb44ca7c350026a5d27e679cf85c0c8df9e69db28387b06", tx.Hash().Hex()) } func TestSnakeData(t *testing.T) { @@ -262,6 +383,38 @@ func TestSnakeData(t *testing.T) { } } +func TestDeployment(t *testing.T) { + // ARRANGE + // Given TSS address & Authority address + const ( + sampleTSSPrivateKey = "0xb984cd65727cfd03081fc7bf33bf5c208bca697ce16139b5ded275887e81395a" + sampleAuthority = "0:4686a2c066c784a915f3e01c853d3195ed254c948e21adbb3e4a9b3f5f3c74d7" + ) + + privateKey := evmWallet(t, sampleTSSPrivateKey) + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + require.True(t, ok) + + tss := crypto.PubkeyToAddress(*publicKeyECDSA) + + // ACT + code := GatewayCode() + stateInit := GatewayStateInit(ton.MustParseAccountID(sampleAuthority), tss, true) + + // ASSERT + _, err := code.ToBocStringCustom(false, true, false, 0) + require.NoError(t, err) + + stateString, err := stateInit.ToBocStringCustom(false, true, false, 0) + require.NoError(t, err) + + // Taken from jest tests in protocol-contracts-ton (using the same vars for initState config) + const expectedState = "b5ee9c7241010101003c000074800000000124d38a790fdf1d9311fae87d4b21aeffd77bc26c004686a2c066c784a915f3e01c853d3195ed254c948e21adbb3e4a9b3f5f3c74d746f17671" + + require.Equal(t, expectedState, stateString) +} + //go:embed testdata var fixtures embed.FS @@ -311,3 +464,13 @@ func readFixtureFile(t *testing.T, filename string) []byte { return b } + +func evmWallet(t *testing.T, privateKey string) *ecdsa.PrivateKey { + pkBytes, err := hex.DecodeString(privateKey[2:]) + require.NoError(t, err) + + pk, err := crypto.ToECDSA(pkBytes) + require.NoError(t, err) + + return pk +} diff --git a/pkg/contracts/ton/gateway_tx.go b/pkg/contracts/ton/gateway_tx.go index 75c12c8eff..e25872d241 100644 --- a/pkg/contracts/ton/gateway_tx.go +++ b/pkg/contracts/ton/gateway_tx.go @@ -10,6 +10,7 @@ import ( type Transaction struct { ton.Transaction Operation Op + ExitCode int32 content any inbound bool @@ -20,6 +21,11 @@ func (tx *Transaction) IsInbound() bool { return tx.inbound } +// IsOutbound returns true if the transaction is outbound. +func (tx *Transaction) IsOutbound() bool { + return !tx.inbound +} + // GasUsed returns the amount of gas used by the transaction. func (tx *Transaction) GasUsed() math.Uint { return math.NewUint(uint64(tx.TotalFees.Grams)) @@ -40,6 +46,11 @@ func (tx *Transaction) DepositAndCall() (DepositAndCall, error) { return retrieveContent[DepositAndCall](tx) } +// Withdrawal casts the transaction content to a Withdrawal. +func (tx *Transaction) Withdrawal() (Withdrawal, error) { + return retrieveContent[Withdrawal](tx) +} + func retrieveContent[T any](tx *Transaction) (T, error) { typed, ok := tx.content.(T) if !ok { diff --git a/pkg/contracts/ton/testdata/00-donation.json b/pkg/contracts/ton/testdata/00-donation.json index 867f096a90..5e9ab7312d 100644 --- a/pkg/contracts/ton/testdata/00-donation.json +++ b/pkg/contracts/ton/testdata/00-donation.json @@ -1,8 +1,8 @@ { - "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b", - "boc": "b5ee9c72010207010001a10003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017d46c458143cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf000017ab22a3b30366f17efd000146114e1a80102030101a00400827213c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0a7b38b0f8722a81351a279e9d229e4f5d92a5d91aed6c197ab4b5ef756b042cc021b04c0731749165a0bc01860db5611050600c968012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d165a0bc000608235a00002fa8d88b0284cde2fdfa00000032000000000000000040009e408c6c3d090000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04", - "description": "Sample donation to gw contract. https://testnet.tonviewer.com/transaction/d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea", - "hash": "d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea", - "logicalTime": 26201117000003, + "account": "0:8bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99", + "boc": "b5ee9c720102070100019f0003b578bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df9900001897c5b37203f893d7ed7fc3d73aedb44ca7c350026a5d27e679cf85c0c8df9e69db28387b0600001897be5e9d4367110c250001460ef51680102030101a0040082728ae60f54fbd7891d1dc0cce8ca7e5dba50607abb642c57079270694d7b4fcd199318244a0da46af5b8f09ec45994a6520b84fb68e915d568c80bf9485d03e5b40217045ec908f0d1801860ef4211050600c968012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f0022f303199741f9638ced5bb5aaac086541c76fc230fdb802fd7c90336c2977e6508f0d18000608235a0000312f8b66e404ce22184a00000032000000000000000040009e40992c3d090000000000000000002100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04", + "description": "Donate ~0.6 TON; https://testnet.tonviewer.com/transaction/924a0996125ed491157b1398d5192bd371a2677df4b60a36ac94d8370c23c94d", + "hash": "924a0996125ed491157b1398d5192bd371a2677df4b60a36ac94d8370c23c94d", + "logicalTime": 27040136000003, "test": true } \ No newline at end of file diff --git a/pkg/contracts/ton/testdata/01-deposit.json b/pkg/contracts/ton/testdata/01-deposit.json index d22170ddf0..78f1b53f94 100644 --- a/pkg/contracts/ton/testdata/01-deposit.json +++ b/pkg/contracts/ton/testdata/01-deposit.json @@ -1,8 +1,8 @@ { - "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b", - "boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04", - "description": "Sample deposit to gw contract. https://testnet.tonviewer.com/transaction/cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf", - "hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf", - "logicalTime": 26023788000003, + "account": "0:8bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99", + "boc": "b5ee9c7201020a010001ff0003b578bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df9900001897be5e9d437fd807ff43c2da1f30cf6ebda98e5de8d54d6b5b644405d34501772896bd915b00001897bd98400367110b0000034672e48080102030201e00405008272a3610b9408c63364d64dccd4b442629796edf1843c0c2279d3d219a10a2ea4028ae60f54fbd7891d1dc0cce8ca7e5dba50607abb642c57079270694d7b4fcd19021904221a90ee6b28018664af0110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f0022f303199741f9638ced5bb5aaac086541c76fc230fdb802fd7c90336c2977e650ee6b28000608235a0000312f7cbd3a84ce22160000000032800000000000000050f5c6b2dbb2e92cf3a905bc8de23c1d6f7eeccc400101df06015de0045e606332e83f2c719dab76b555810ca838edf8461fb7005faf92066d852efcc80000312f7cbd3a88ce221600c007001043b5dc10033d0900009e44070c3d09000000000000000000b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9838d604c1c6b0000000000000200000000000360161b93272c05ec52632c491b53e07b47cbbc930d19187ffb87a6961e0bf72240900d8c", + "description": "Deposit 1 TON; https://testnet.tonviewer.com/transaction/f893d7ed7fc3d73aedb44ca7c350026a5d27e679cf85c0c8df9e69db28387b06", + "hash": "f893d7ed7fc3d73aedb44ca7c350026a5d27e679cf85c0c8df9e69db28387b06", + "logicalTime": 27040013000003, "test": true -} +} \ No newline at end of file diff --git a/pkg/contracts/ton/testdata/02-deposit-and-call.json b/pkg/contracts/ton/testdata/02-deposit-and-call.json index 60f824a828..0e73f845a0 100644 --- a/pkg/contracts/ton/testdata/02-deposit-and-call.json +++ b/pkg/contracts/ton/testdata/02-deposit-and-call.json @@ -1,8 +1,8 @@ { - "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b", - "boc": "b5ee9c7201021a01000a040003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017d4acf14a83d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea000017d46c45814366f1896b000347487d2080102030201e00405008272a7b38b0f8722a81351a279e9d229e4f5d92a5d91aed6c197ab4b5ef756b042ccdb7075fb2e3f1dc397aa3c93d8dd352cbe54af9fb033df63082875f03a70947102190480b409077359401866309211181901f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d077359400069397a000002fa959e29504cde312d60000003300000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c0080101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002fa959e29508cde312d6c007018b0000006600000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e83a699d01b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b0801fe57686174206973204c6f72656d20497073756d3f2028617070726f782032204b696c6f6279746573290a0a4c6f72656d20497073756d2069732073696d706c792064756d6d792074657874206f6620746865207072696e74696e6720616e64207479706573657474696e6720696e6475737472792e0a4c6f72656d204970730901fe756d20686173206265656e2074686520696e6475737472792773207374616e646172642064756d6d79207465787420657665722073696e6365207468652031353030732c207768656e20616e20756e6b6e6f776e207072696e74657220746f6f6b0a612067616c6c6579206f66207479706520616e6420736372616d626c650a01fe6420697420746f206d616b65206120747970652073706563696d656e20626f6f6b2e20497420686173207375727669766564206e6f74206f6e6c7920666976652063656e7475726965732c0a62757420616c736f20746865206c65617020696e746f20656c656374726f6e6963207479706573657474696e672c2072656d610b01fe696e696e6720657373656e7469616c6c7920756e6368616e6765642e0a0a49742077617320706f70756c61726973656420696e207468652031393630732077697468207468652072656c65617365206f66204c657472617365742073686565747320636f6e7461696e696e67204c6f72656d20497073756d207061737361670c01fe65732c0a616e64206d6f726520726563656e746c792077697468206465736b746f70207075626c697368696e6720736f667477617265206c696b6520416c64757320506167654d616b657220696e636c7564696e672076657273696f6e73206f66204c6f72656d20497073756d2e0a0a57687920646f2077652075736520690d01fe743f0a0a49742069732061206c6f6e672d65737461626c6973686564206661637420746861742061207265616465722077696c6c206265206469737472616374656420627920746865207265616461626c6520636f6e74656e74206f6620612070616765207768656e0a6c6f6f6b696e6720617420697473206c61796f75740e01fe2e2054686520706f696e74206f66207573696e67204c6f72656d20497073756d2069732074686174206974206861732061206d6f72652d6f722d6c657373206e6f726d616c20646973747269627574696f6e206f66206c6574746572732c0a6173206f70706f73656420746f207573696e672027436f6e74656e74206865720f01fe652c20636f6e74656e742068657265272c206d616b696e67206974206c6f6f6b206c696b65207265616461626c6520456e676c6973682e204d616e79206465736b746f70207075626c697368696e670a7061636b6167657320616e6420776562207061676520656469746f7273206e6f7720757365204c6f72656d204970731001fe756d2061732074686569722064656661756c74206d6f64656c20746578742c20616e6420612073656172636820666f7220276c6f72656d20697073756d270a77696c6c20756e636f766572206d616e7920776562207369746573207374696c6c20696e20746865697220696e66616e63792e20566172696f757320766572731101fe696f6e7320686176652065766f6c766564206f766572207468652079656172732c20736f6d6574696d65730a6279206163636964656e742c20736f6d6574696d6573206f6e20707572706f73652028696e6a65637465642068756d6f757220616e6420746865206c696b65292e0a0a576865726520646f657320697420636f1201fe6d652066726f6d3f0a0a436f6e747261727920746f20706f70756c61722062656c6965662c204c6f72656d20497073756d206973206e6f742073696d706c792072616e646f6d20746578742e2049742068617320726f6f747320696e2061207069656365206f6620636c6173736963616c0a4c6174696e206c6974657261741301fe7572652066726f6d2034352042432c206d616b696e67206974206f7665722032303030207965617273206f6c642e2052696368617264204d63436c696e746f636b2c2061204c6174696e2070726f666573736f720a61742048616d7064656e2d5379646e657920436f6c6c65676520696e2056697267696e69612c206c6f6f1401fe6b6564207570206f6e65206f6620746865206d6f7265206f627363757265204c6174696e20776f7264732c20636f6e73656374657475722c0a66726f6d2061204c6f72656d20497073756d20706173736167652c20616e6420676f696e67207468726f75676820746865206369746573206f662074686520776f726420696e1501fe20636c6173736963616c206c6974657261747572652c20646973636f76657265640a74686520756e646f75627461626c6520736f757263652e204c6f72656d20497073756d20636f6d65732066726f6d2073656374696f6e7320312e31302e333220616e6420312e31302e3333206f66202264652046696e6962757320426f1601fe6e6f72756d206574204d616c6f72756d220a285468652045787472656d6573206f6620476f6f6420616e64204576696c292062792043696365726f2c207772697474656e20696e2034352042432e205468697320626f6f6b2069732061207472656174697365206f6e20746865207468656f7279206f66206574686963732c17004a0a7665727920706f70756c617220647572696e67207468652052656e61697373616e63652e009e43f62c3d090000000000000000009b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9b95b984dcadcc0000000000002000000000003fa4c79ea2219e757506c681b3ab938299662dccbcef3f334edcddee1cd13bade44920284", - "description": "Sample deposit-and-call to gw contract. https://testnet.tonviewer.com/transaction/3647f17cc28e4a70404a10c62ad6262fbf67aa72579acde449d66cc0d0fd7ca8", - "hash": "3647f17cc28e4a70404a10c62ad6262fbf67aa72579acde449d66cc0d0fd7ca8", - "logicalTime": 26202202000003, + "account": "0:8bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99", + "boc": "b5ee9c7201020b010002380003b578bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df9900001897f1271a43924a0996125ed491157b1398d5192bd371a2677df4b60a36ac94d8370c23c94d00001897c5b37203671113570003467ef32680102030201e004050082729318244a0da46af5b8f09ec45994a6520b84fb68e915d568c80bf9485d03e5b4dfb5d92afc4cc4b8a9ee7bd8e44b1cb4e83f8ae846b86103d1b13a885bf6d99302190480acc905f5e10018670b8411090a01f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f0022f303199741f9638ced5bb5aaac086541c76fc230fdb802fd7c90336c2977e6505f5e1000060c77b20000312fe24e3484ce2226ae00000033000000000000000050f5c6b2dbb2e92cf3a905bc8de23c1d6f7eeccc40060101df07006c546865736520224e46547322202d20617265207468657920696e2074686520726f6f6d2077697468207573207269676874206e6f773f015de0045e606332e83f2c719dab76b555810ca838edf8461fb7005faf92066d852efcc80000312fe24e3488ce2226aec0080010417882b8034f5880009e44824c3d09000000000000000000da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9838d604c1c6b000000000000020000000000029cbe8eaac8bfc6c3c14f724ae49966bef85efe4d41702b512100dd68514496de40900d8c", + "description": "Deposit 0.4 TON and add some call data; https://testnet.tonviewer.com/transaction/b6e26611d75a96a67d39a61958b81a940c2b2e6cb7e51798bb9cccc247eda6d7", + "hash": "b6e26611d75a96a67d39a61958b81a940c2b2e6cb7e51798bb9cccc247eda6d7", + "logicalTime": 27040865000003, "test": true } \ No newline at end of file diff --git a/pkg/contracts/ton/testdata/04-bounced-msg.json b/pkg/contracts/ton/testdata/04-bounced-msg.json index 9887db3123..afbef3199a 100644 --- a/pkg/contracts/ton/testdata/04-bounced-msg.json +++ b/pkg/contracts/ton/testdata/04-bounced-msg.json @@ -1,8 +1,8 @@ { - "account": "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f", - "boc": "b5ee9c72010207010001a30003b579594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f000017d5af81eb05b4269279feefdc3ea4220465ec2bce20c6e7f0a8c09b51dac55b90aef3c342c6000017d5af81eb0166f1b2b0000146097f4080102030101a0040082727682d6ce21cbeaec70573e8d6ab7dc6357b875cbffc8c50608035fda3fe3bc378db05a075336855e0ae2db0067bf7de2341a87b9d555e968d64b216fe5491aba02150c090179e568186097f411050600d3580132fb113902b5d584388ff0d5c1c70707df87868c60ceb05ec4c75a918b4290b700256531c67b13257d99a0ecbec7282c27792907dbec21ee93234996e5a9333943d0179e56800608235a00002fab5f03d608cde365607fffffffa432b6363796102bb7b9363210c0009e40614c0f1da800000000000000001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04", - "description": "Sample bounced message. This address is not even a gw. https://testnet.tonviewer.com/transaction/b3c46f5faf8aee7348083e7adbbc9a60ab1c8e0eac09133d64e2c4eb831e607b", - "hash": "b3c46f5faf8aee7348083e7adbbc9a60ab1c8e0eac09133d64e2c4eb831e607b", - "logicalTime": 26206540000005, + "account": "0:8bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99", + "boc": "b5ee9c72010208010001ce0003b578bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df9900001897fbd26d03b6e26611d75a96a67d39a61958b81a940c2b2e6cb7e51798bb9cccc247eda6d700001897f1271a436711152300034610fd4080102030201e00405008272dfb5d92afc4cc4b8a9ee7bd8e44b1cb4e83f8ae846b86103d1b13a885bf6d99374f73160896d4a44a6716af0cbffd5188134aad0f5ad71e85b3d997e93d2f2b30127046b49077359401060cea40e0181046998208d6a0700b168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f0022f303199741f9638ced5bb5aaac086541c76fc230fdb802fd7c90336c2977e650773594000608235a0000312ff7a4da04ce222a46400101df0600b95801179818ccba0fcb1c676addad5560432a0e3b7e1187edc017ebe4819b614bbf3300256531c67b13257d99a0ecbec7282c27792907dbec21ee93234996e5a9333943d0770355800608235a0000312ff7a4da08ce222a467fffffffc0009e40844c3d090000000000ca0000001600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "description": "Sent & bounce 0.5 TON w/o op code; https://testnet.tonviewer.com/transaction/45c1d3af95531f21f89e7ae358cdb8676be6d115d1452cfecc0afba504b2865c", + "hash": "45c1d3af95531f21f89e7ae358cdb8676be6d115d1452cfecc0afba504b2865c", + "logicalTime": 27041044000003, "test": true -} +} \ No newline at end of file diff --git a/pkg/contracts/ton/testdata/06-withdrawal.json b/pkg/contracts/ton/testdata/06-withdrawal.json new file mode 100644 index 0000000000..4a1c439049 --- /dev/null +++ b/pkg/contracts/ton/testdata/06-withdrawal.json @@ -0,0 +1,8 @@ +{ + "account": "0:8bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99", + "boc": "b5ee9c7201020a010002330003b578bcc0c665d07e58e33b56ed6aab02195071dbf08c3f6e00bf5f240cdb0a5df99000018c1045da241a4c5249c3137d2a34959c24e17559ce9ac2db1ab50bf56ff90c51c699be1a101000018c1044e60016717d9b20003469b312880102030201e0040500827297289c46e054cc4185bbc479cec317dd3f434b6ad3c944d3d03498edd9f292cfb5c9d220a628308d45ba04ca3933aa8af3980597f6bde7cb357e24dca0cbe019020f0c40461a15408440080901c78801179818ccba0fcb1c676addad5560432a0e3b7e1187edc017ebe4819b614bbf3200d9a3d1d692aeaa822e70fbb03323f2547486ab0de48411a5e254b57a01007695383cfc2b0a381d566e8a16ff1f59507eaf4600cf5b3d642f0845f8635b9c82432c060101df070059000000c8801a1ceb92cd3e39501d1982f0ce073e3598ce86d963f913b280da9a36063f66d967312d000000000300af4801179818ccba0fcb1c676addad5560432a0e3b7e1187edc017ebe4819b614bbf33003439d7259a7c72a03a3305e19c0e7c6b319d0db2c7f2276501b5346c0c7ecdb2ce625a000608235a0000318208bb4484ce2fb36440009d45552313880000000000000000384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020006fc9830d404c08234c0000000000020000000000039c9b8959c7d6f61f0cdcae7c604fb1eec3ce2c80c6e17335d9b1c705edf34ba4405015cc", + "description": "Withdrawal of 0.01 TON; https://testnet.tonviewer.com/transaction/ea60180151e4449af44438d93e13f2451820245cac5a9e2fa44ff1f2d78118fa", + "hash": "ea60180151e4449af44438d93e13f2451820245cac5a9e2fa44ff1f2d78118fa", + "logicalTime": 27217281000001, + "test": true +} \ No newline at end of file diff --git a/pkg/ticker/ticker.go b/pkg/ticker/ticker.go index 94d4a87efc..ebac3e1a96 100644 --- a/pkg/ticker/ticker.go +++ b/pkg/ticker/ticker.go @@ -66,7 +66,7 @@ type Opt func(*Ticker) // WithLogger sets the logger for the Ticker. func WithLogger(log zerolog.Logger, name string) Opt { return func(t *Ticker) { - t.logger = log.With().Str("ticker_name", name).Logger() + t.logger = log.With().Str("ticker.name", name).Logger() } } @@ -82,6 +82,7 @@ func New(interval time.Duration, task Task, opts ...Opt) *Ticker { t := &Ticker{ interval: interval, task: task, + logger: zerolog.Nop(), } for _, opt := range opts { @@ -162,6 +163,11 @@ func (t *Ticker) SetInterval(interval time.Duration) { return } + t.logger.Info(). + Dur("ticker.old_interval", t.interval). + Dur("ticker.new_interval", interval). + Msg("Changing interval") + t.interval = interval t.ticker.Reset(interval) } diff --git a/pkg/ticker/ticker_test.go b/pkg/ticker/ticker_test.go index 60d6c74dc8..4e03a28d4f 100644 --- a/pkg/ticker/ticker_test.go +++ b/pkg/ticker/ticker_test.go @@ -245,6 +245,6 @@ func TestTicker(t *testing.T) { // ARRANGE require.ErrorContains(t, err, "hey") - require.Contains(t, out.String(), `{"level":"info","ticker_name":"my-task","message":"Ticker stopped"}`) + require.Contains(t, out.String(), `{"level":"info","ticker.name":"my-task","message":"Ticker stopped"}`) }) } diff --git a/testutil/sample/sample_ton.go b/testutil/sample/ton.go similarity index 71% rename from testutil/sample/sample_ton.go rename to testutil/sample/ton.go index 01ce8724c5..4e1627f394 100644 --- a/testutil/sample/sample_ton.go +++ b/testutil/sample/ton.go @@ -8,7 +8,6 @@ import ( "unsafe" "cosmossdk.io/math" - eth "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/tonkeeper/tongo/boc" "github.com/tonkeeper/tongo/tlb" @@ -18,16 +17,17 @@ import ( ) const ( - tonWorkchainID = 0 - tonShardID = 123 - tonDepositFee = 10_000_000 // 0.01 TON - tonSampleGasUsage = 50_000_000 // 0.05 TON + tonWorkchainID = 0 + tonShardID = 123 + tonSampleTxFee = 006_500_000 // 0.0065 TON + tonSampleGasUsed = 8500 ) type TONTransactionProps struct { - Account ton.AccountID - GasUsed uint64 - BlockID ton.BlockIDExt + Account ton.AccountID + GasUsed uint64 + TotalTONFees uint64 + BlockID ton.BlockIDExt // For simplicity let's have only one input // and one output (both optional) @@ -56,7 +56,7 @@ func TONDonateProps(t *testing.T, acc ton.AccountID, d toncontracts.Donation) TO body, err := d.AsBody() require.NoError(t, err) - deposited := tonSampleGasUsage + d.Amount.Uint64() + tonSent := tonSampleTxFee + d.Amount.Uint64() return TONTransactionProps{ Account: acc, @@ -65,7 +65,7 @@ func TONDonateProps(t *testing.T, acc ton.AccountID, d toncontracts.Donation) TO Bounce: true, Src: d.Sender.ToMsgAddress(), Dest: acc.ToMsgAddress(), - Value: tlb.CurrencyCollection{Grams: tlb.Grams(deposited)}, + Value: tlb.CurrencyCollection{Grams: tlb.Grams(tonSent)}, }), Body: tlb.EitherRef[tlb.Any]{Value: tlb.Any(*body)}, }, @@ -80,7 +80,7 @@ func TONDepositProps(t *testing.T, acc ton.AccountID, d toncontracts.Deposit) TO body, err := d.AsBody() require.NoError(t, err) - logBody := depositLogMock(t, d.Sender, d.Amount.Uint64(), d.Recipient, nil) + logBody := depositLogMock(t, d.Amount.Uint64(), tonSampleTxFee) return TONTransactionProps{ Account: acc, @@ -107,7 +107,7 @@ func TONDepositAndCallProps(t *testing.T, acc ton.AccountID, d toncontracts.Depo body, err := d.AsBody() require.NoError(t, err) - logBody := depositLogMock(t, d.Sender, d.Amount.Uint64(), d.Recipient, d.CallData) + logBody := depositLogMock(t, d.Amount.Uint64(), tonSampleTxFee) return TONTransactionProps{ Account: acc, @@ -126,6 +126,31 @@ func TONDepositAndCallProps(t *testing.T, acc ton.AccountID, d toncontracts.Depo } } +func TONWithdrawal(t *testing.T, acc ton.AccountID, w toncontracts.Withdrawal) ton.Transaction { + return TONTransaction(t, TONWithdrawalProps(t, acc, w)) +} + +func TONWithdrawalProps(t *testing.T, acc ton.AccountID, w toncontracts.Withdrawal) TONTransactionProps { + body, err := w.AsBody() + require.NoError(t, err) + + return TONTransactionProps{ + Account: acc, + Input: &tlb.Message{ + Info: externalMessageInfo(acc), + Body: tlb.EitherRef[tlb.Any]{Value: tlb.Any(*body)}, + }, + Output: &tlb.Message{ + Info: internalMessageInfo(&intMsgInfo{ + IhrDisabled: true, + Src: acc.ToMsgAddress(), + Dest: w.Recipient.ToMsgAddress(), + Value: tlb.CurrencyCollection{Grams: tlb.Coins(w.Amount.Uint64())}, + }), + }, + } +} + // TONTransaction creates a sample TON transaction. func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction { require.False(t, p.Account.IsZero(), "account address is empty") @@ -134,7 +159,11 @@ func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction { now := time.Now().UTC() if p.GasUsed == 0 { - p.GasUsed = tonSampleGasUsage + p.GasUsed = tonSampleGasUsed + } + + if p.TotalTONFees == 0 { + p.TotalTONFees = tonSampleTxFee } if p.BlockID.BlockID.Seqno == 0 { @@ -170,7 +199,7 @@ func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction { Lt: lt, Now: uint32(now.Unix()), OutMsgCnt: tlb.Uint15(len(outputs.Keys())), - TotalFees: tlb.CurrencyCollection{Grams: tlb.Grams(p.GasUsed)}, + TotalFees: tlb.CurrencyCollection{Grams: tlb.Grams(p.TotalTONFees)}, Msgs: messages{InMsg: input, OutMsgs: outputs}, }, } @@ -207,6 +236,20 @@ func internalMessageInfo(info *intMsgInfo) tlb.CommonMsgInfo { } } +func externalMessageInfo(dest ton.AccountID) tlb.CommonMsgInfo { + ext := struct { + Src tlb.MsgAddress + Dest tlb.MsgAddress + ImportFee tlb.VarUInteger16 + }{ + Src: tlb.MsgAddress{SumType: "AddrNone"}, + Dest: dest.ToMsgAddress(), + ImportFee: tlb.VarUInteger16{}, + } + + return tlb.CommonMsgInfo{SumType: "ExtInMsgInfo", ExtInMsgInfo: &ext} +} + func tonBlockID(now time.Time) ton.BlockIDExt { // simulate shard seqno as unix timestamp seqno := uint32(now.Unix()) @@ -221,42 +264,19 @@ func tonBlockID(now time.Time) ton.BlockIDExt { } func fakeDepositAmount(v math.Uint) tlb.Grams { - return tlb.Grams(v.Uint64() + tonDepositFee) + return tlb.Grams(v.Uint64() + tonSampleTxFee) } -func depositLogMock( - t *testing.T, - sender ton.AccountID, - amount uint64, - recipient eth.Address, - callData []byte, -) *boc.Cell { - // cell log = begin_cell() - // .store_uint(op::internal::deposit_and_call, size::op_code_size) - // .store_uint(0, size::query_id_size) - // .store_slice(sender) - // .store_coins(deposit_amount) - // .store_uint(evm_recipient, size::evm_address) - // .store_ref(call_data) // only for DepositAndCall - // .end_cell(); +func depositLogMock(t *testing.T, depositAmount, txFee uint64) *boc.Cell { + // cell log = begin_cell() + // .store_coins(deposit_amount) + // .store_coins(tx_fee) + // .end_cell(); b := boc.NewCell() - require.NoError(t, b.WriteUint(0, 32+64)) - - // skip - msgAddr := sender.ToMsgAddress() - require.NoError(t, tlb.Marshal(b, msgAddr)) - coins := tlb.Grams(amount) - require.NoError(t, coins.MarshalTLB(b, nil)) - - require.NoError(t, b.WriteBytes(recipient.Bytes())) - - if callData != nil { - callDataCell, err := toncontracts.MarshalSnakeCell(callData) - require.NoError(t, err) - require.NoError(t, b.AddRef(callDataCell)) - } + require.NoError(t, tlb.Grams(depositAmount).MarshalTLB(b, nil)) + require.NoError(t, tlb.Grams(txFee).MarshalTLB(b, nil)) return b } diff --git a/testutil/sample/sample_ton_test.go b/testutil/sample/ton_test.go similarity index 100% rename from testutil/sample/sample_ton_test.go rename to testutil/sample/ton_test.go diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go index f089f26815..67f38e627a 100644 --- a/zetaclient/chains/base/observer.go +++ b/zetaclient/chains/base/observer.go @@ -506,21 +506,25 @@ func (ob *Observer) PostVoteInbound( // AlertOnRPCLatency prints an alert if the RPC latency exceeds the threshold. // Returns true if the RPC latency is too high. func (ob *Observer) AlertOnRPCLatency(latestBlockTime time.Time, defaultAlertLatency time.Duration) bool { - // use configured alert latency if set - alertLatency := defaultAlertLatency - if ob.rpcAlertLatency > 0 { - alertLatency = ob.rpcAlertLatency + elapsedTime := time.Since(latestBlockTime) + + alertLatency := ob.rpcAlertLatency + if alertLatency == 0 { + alertLatency = defaultAlertLatency + } + + lf := map[string]any{ + "rpc_latency_alert_ms": alertLatency.Milliseconds(), + "rpc_latency_real_ms": elapsedTime.Milliseconds(), } - // latest block should not be too old - elapsedTime := time.Since(latestBlockTime) if elapsedTime > alertLatency { - ob.logger.Chain.Error(). - Msgf("RPC is stale: latest block is %.0f seconds old, RPC down or chain stuck (check explorer)?", elapsedTime.Seconds()) + ob.logger.Chain.Error().Fields(lf).Msg("RPC latency is too high, please check the node or explorer") return true } - ob.logger.Chain.Info().Msgf("RPC is OK: latest block is %.0f seconds old", elapsedTime.Seconds()) + ob.logger.Chain.Info().Fields(lf).Msg("RPC latency is OK") + return false } diff --git a/zetaclient/chains/ton/config.go b/zetaclient/chains/ton/config.go index 731287756e..c3c0e3da3e 100644 --- a/zetaclient/chains/ton/config.go +++ b/zetaclient/chains/ton/config.go @@ -7,11 +7,23 @@ import ( "net/url" "time" + "github.com/pkg/errors" "github.com/tonkeeper/tongo/config" + "github.com/tonkeeper/tongo/liteapi" + "github.com/tonkeeper/tongo/tlb" ) type GlobalConfigurationFile = config.GlobalConfigurationFile +// ConfigGetter represents LiteAPI config params getter. +// Don't be confused because config param in this case represent on-chain params, +// not lite-client's ADNL json config to connect to the network. +// +// Read more at https://docs.ton.org/develop/howto/blockchain-configs +type ConfigGetter interface { + GetConfigParams(ctx context.Context, mode liteapi.ConfigMode, params []uint32) (tlb.ConfigParams, error) +} + // ConfigFromURL downloads & parses lite server config. // //nolint:gosec @@ -52,3 +64,67 @@ func ConfigFromSource(ctx context.Context, urlOrPath string) (*GlobalConfigurati return ConfigFromPath(urlOrPath) } + +// FetchGasConfig fetches gas price from the config. +func FetchGasConfig(ctx context.Context, getter ConfigGetter) (tlb.GasLimitsPrices, error) { + // https://docs.ton.org/develop/howto/blockchain-configs + // https://tonviewer.com/config#21 + const configKeyGas = 21 + + response, err := getter.GetConfigParams(ctx, 0, []uint32{configKeyGas}) + if err != nil { + return tlb.GasLimitsPrices{}, errors.Wrap(err, "failed to get config params") + } + + ref, ok := response.Config.Get(configKeyGas) + if !ok { + return tlb.GasLimitsPrices{}, errors.Errorf("config key %d not found", configKeyGas) + } + + var cfg tlb.ConfigParam21 + if err = tlb.Unmarshal(&ref.Value, &cfg); err != nil { + return tlb.GasLimitsPrices{}, errors.Wrap(err, "failed to unmarshal config param") + } + + return cfg.GasLimitsPrices, nil +} + +// ParseGasPrice parses gas price from the config and returns price in tons per 1 gas unit. +// You can take a look at definitions here: +// https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb +// https://docs.ton.org/develop/howto/blockchain-configs#param-20-and-21 +// +// gas_prices#dd gas_price:uint64 gas_limit:uint64 gas_credit:uint64 +// block_gas_limit:uint64 freeze_due_limit:uint64 delete_due_limit:uint64 = GasLimitsPrices; +// +// gas_prices_ext#de gas_price:uint64 gas_limit:uint64 special_gas_limit:uint64 gas_credit:uint64 +// block_gas_limit:uint64 freeze_due_limit:uint64 delete_due_limit:uint64 = GasLimitsPrices; +// +// gas_flat_pfx#d1 flat_gas_limit:uint64 flat_gas_price:uint64 other:GasLimitsPrices = GasLimitsPrices; +func ParseGasPrice(cfg tlb.GasLimitsPrices) (uint64, error) { + // tongo lib uses a concept of "sum types" + // to decode different (sub)type of entities. + // Basically, sumType is a struct property that is not empty (i.e. decoded). + const ( + sumTypeGasPrices = "GasPrices" + sumTypeGasPricesExt = "GasPricesExt" + sumTypeGasFlatPfx = "GasFlatPfx" + ) + + // from TON docs: gas_price: This parameter reflects + // the price of gas in the network, in nano tons per 65536 gas units (2^16). + // We have 3 cases because TON node might return on of these 3 structs. + switch cfg.SumType { + case sumTypeGasPrices: + return cfg.GasPrices.GasPrice >> 16, nil + case sumTypeGasPricesExt: + return cfg.GasPricesExt.GasPrice >> 16, nil + case sumTypeGasFlatPfx: + if cfg.GasFlatPfx.Other == nil { + return 0, errors.New("GasFlatPfx.Other is nil") + } + return ParseGasPrice(*cfg.GasFlatPfx.Other) + default: + return 0, errors.Errorf("unknown SumType: %q", cfg.SumType) + } +} diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go index 25b0efcf39..53f585899f 100644 --- a/zetaclient/chains/ton/liteapi/client.go +++ b/zetaclient/chains/ton/liteapi/client.go @@ -28,6 +28,8 @@ const ( blockCacheSize = 250 ) +var ErrNotFound = errors.New("not found") + // New Client constructor. func New(client *liteapi.Client) *Client { blockCache, _ := lru.New(blockCacheSize) @@ -189,6 +191,25 @@ func (c *Client) GetTransactionsSince( return result, nil } +// GetTransaction returns account's tx by logicalTime and hash or ErrNotFound. +func (c *Client) GetTransaction( + ctx context.Context, + acc ton.AccountID, + lt uint64, + hash ton.Bits256, +) (ton.Transaction, error) { + txs, err := c.GetTransactions(ctx, 1, acc, lt, hash) + if err != nil { + return ton.Transaction{}, err + } + + if len(txs) == 0 { + return ton.Transaction{}, ErrNotFound + } + + return txs[0], nil +} + // getLastTransactionHash returns logical time and hash of the last transaction func (c *Client) getLastTransactionHash(ctx context.Context, acc ton.AccountID) (uint64, tlb.Bits256, error) { state, err := c.GetAccountState(ctx, acc) @@ -203,6 +224,12 @@ func (c *Client) getLastTransactionHash(ctx context.Context, acc ton.AccountID) return state.LastTransLt, state.LastTransHash, nil } +// TransactionToHashString converts transaction's logicalTime and hash to string +// This string is used to store the last scanned hash (e.g. "123:0x123...") +func TransactionToHashString(tx ton.Transaction) string { + return TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash())) +} + // TransactionHashToString converts logicalTime and hash to string func TransactionHashToString(lt uint64, hash ton.Bits256) string { return fmt.Sprintf("%d:%s", lt, hash.Hex()) diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go index ed3c850dd8..f77f08420b 100644 --- a/zetaclient/chains/ton/liteapi/client_live_test.go +++ b/zetaclient/chains/ton/liteapi/client_live_test.go @@ -8,12 +8,15 @@ import ( "testing" "time" + "cosmossdk.io/math" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tonkeeper/tongo/config" "github.com/tonkeeper/tongo/liteapi" "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + zetaton "github.com/zeta-chain/node/zetaclient/chains/ton" "github.com/zeta-chain/node/zetaclient/common" ) @@ -136,6 +139,55 @@ func TestClient(t *testing.T) { require.NotZero(t, header.MinRefMcSeqno) require.Equal(t, header.MinRefMcSeqno, header.MasterRef.Master.SeqNo) }) + + t.Run("GetMasterchainInfo", func(t *testing.T) { + // ARRANGE + // all bits are 1 (0xFFF...) or also `-1` in TON's notation + const masterChain = uint32(1<<32 - 1) + + // ACT #1 + mc, err := client.GetMasterchainInfo(ctx) + + // ASSERT #1 + require.NoError(t, err) + require.Equal(t, masterChain, mc.Last.Workchain) + + // ACT #2 + block, err := client.GetBlockHeader(ctx, mc.Last.ToBlockIdExt(), 0) + + // ASSERT #2 + require.NoError(t, err) + require.False(t, block.NotMaster) + + // Check that block was generated less than 10 seconds ago + blockTime := time.Unix(int64(block.GenUtime), 0).UTC() + since := time.Since(blockTime) + + assert.LessOrEqual(t, since, 20*time.Second) + + t.Logf("Masterchain block #%d is generated at %q (%s ago)", block.SeqNo, blockTime, since.String()) + }) + + t.Run("GetGasConfig", func(t *testing.T) { + // ACT #1 + gas, err := zetaton.FetchGasConfig(ctx, client) + + // ASSERT #1 + require.NoError(t, err) + require.NotEmpty(t, gas) + + // ACT #2 + gasPrice, err := zetaton.ParseGasPrice(gas) + + // ASSERT #2 + require.NoError(t, err) + require.NotEmpty(t, gasPrice) + + gasPricePer1000 := math.NewUint(1000 * gasPrice) + + t.Logf("Gas cost: %s per 1000 gas", toncontracts.FormatCoins(gasPricePer1000)) + t.Logf("Compare with https://tonwhales.com/explorer/network") + }) } func mustCreateClient(t *testing.T) *liteapi.Client { diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go index 9a5e6ba548..881853241a 100644 --- a/zetaclient/chains/ton/observer/inbound.go +++ b/zetaclient/chains/ton/observer/inbound.go @@ -23,6 +23,7 @@ const ( MaxTransactionsPerTick = 100 ) +// watchInbound watches for new txs to Gateway's account. func (ob *Observer) watchInbound(ctx context.Context) error { app, err := zctx.FromContext(ctx) if err != nil { @@ -30,21 +31,20 @@ func (ob *Observer) watchInbound(ctx context.Context) error { } var ( - chainID = ob.Chain().ChainId initialInterval = ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) sampledLogger = ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10}) ) - ob.Logger().Inbound.Info().Msgf("WatchInbound started for chain %d", chainID) + ob.Logger().Inbound.Info().Msgf("WatchInbound started") task := func(ctx context.Context, t *ticker.Ticker) error { if !app.IsInboundObservationEnabled() { - sampledLogger.Info().Msgf("WatchInbound: inbound observation is disabled for chain %d", chainID) + sampledLogger.Info().Msg("WatchInbound: inbound observation is disabled") return nil } - if err := ob.observeInbound(ctx); err != nil { - ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error") + if err := ob.observeGateway(ctx); err != nil { + ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeGateway error") } newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) @@ -62,7 +62,11 @@ func (ob *Observer) watchInbound(ctx context.Context) error { ) } -func (ob *Observer) observeInbound(ctx context.Context) error { +// observeGateway observes Gateway's account for new transactions. +// Due to TON architecture we have to scan for all net-new transactions. +// The main purpose is to observe inbounds from TON. +// Note that we might also have *outbounds* here (if a signer broadcasts a tx, it will be observed here). +func (ob *Observer) observeGateway(ctx context.Context) error { if err := ob.ensureLastScannedTX(ctx); err != nil { return errors.Wrap(err, "unable to ensure last scanned tx") } @@ -84,51 +88,79 @@ func (ob *Observer) observeInbound(ctx context.Context) error { return nil case len(txs) > MaxTransactionsPerTick: ob.Logger().Inbound.Info(). - Msgf("observeInbound: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick) + Msgf("observeGateway: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick) txs = txs[:MaxTransactionsPerTick] default: - ob.Logger().Inbound.Info().Msgf("observeInbound: got %d transactions", len(txs)) + ob.Logger().Inbound.Info().Msgf("observeGateway: got %d transactions", len(txs)) } for i := range txs { - tx := txs[i] - - parsedTX, skip, err := ob.gateway.ParseAndFilter(tx, toncontracts.FilterInbounds) - if err != nil { - return errors.Wrap(err, "unable to parse and filter tx") + var skip bool + + tx, err := ob.gateway.ParseTransaction(txs[i]) + switch { + case errors.Is(err, toncontracts.ErrParse) || errors.Is(err, toncontracts.ErrUnknownOp): + skip = true + case err != nil: + // should not happen + return errors.Wrap(err, "unexpected error") + case tx.ExitCode != 0: + skip = true + ob.Logger().Inbound.Warn().Fields(txLogFields(tx)).Msg("observeGateway: observed a failed tx") } if skip { - ob.Logger().Inbound.Info().Fields(txLogFields(&tx)).Msg("observeInbound: skipping tx") - ob.setLastScannedTX(&tx) + tx = &toncontracts.Transaction{Transaction: txs[i]} + txHash := liteapi.TransactionToHashString(tx.Transaction) + ob.Logger().Inbound.Warn().Str("transaction.hash", txHash).Msg("observeGateway: skipping tx") + ob.setLastScannedTX(tx) + continue + } + + // Should not happen + //goland:noinspection GoDfaConstantCondition + if tx == nil { + return errors.New("tx is nil") + } + + // As we might have outbounds here, let's ensure outbound tracker. + // TON signer broadcasts ExtInMsgInfo with `src=null, dest=gateway`, so it will be observed here + if tx.IsOutbound() { + if err = ob.addOutboundTracker(ctx, tx); err != nil { + ob.Logger().Inbound. + Error().Err(err). + Fields(txLogFields(tx)). + Msg("observeGateway: unable to add outbound tracker") + + return errors.Wrap(err, "unable to add outbound tracker") + } + ob.setLastScannedTX(tx) continue } - if _, err := ob.voteInbound(ctx, parsedTX); err != nil { + // Ok, let's process a new inbound tx + if _, err := ob.voteInbound(ctx, tx); err != nil { ob.Logger().Inbound. Error().Err(err). - Fields(txLogFields(&tx)). - Msg("observeInbound: unable to vote for tx") + Fields(txLogFields(tx)). + Msg("observeGateway: unable to vote for inbound tx") return errors.Wrapf(err, "unable to vote for inbound tx %s", tx.Hash().Hex()) } - ob.setLastScannedTX(&parsedTX.Transaction) + ob.setLastScannedTX(tx) } return nil } +// Sends PostVoteInbound to zetacore func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transaction) (string, error) { // noop if tx.Operation == toncontracts.OpDonate { - ob.Logger().Inbound.Info(). - Uint64("tx.lt", tx.Lt). - Str("tx.hash", tx.Hash().Hex()). - Msg("Thank you rich folk for your donation!") - + ob.Logger().Inbound.Info().Fields(txLogFields(tx)).Msg("Thank you rich folk for your donation!") return "", nil } @@ -222,18 +254,18 @@ func (ob *Observer) ensureLastScannedTX(ctx context.Context) error { return nil } - tx, _, err := ob.client.GetFirstTransaction(ctx, ob.gateway.AccountID()) + rawTX, _, err := ob.client.GetFirstTransaction(ctx, ob.gateway.AccountID()) if err != nil { return err } - ob.setLastScannedTX(tx) + ob.setLastScannedTX(&toncontracts.Transaction{Transaction: *rawTX}) return nil } -func (ob *Observer) setLastScannedTX(tx *ton.Transaction) { - txHash := liteapi.TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash())) +func (ob *Observer) setLastScannedTX(tx *toncontracts.Transaction) { + txHash := liteapi.TransactionToHashString(tx.Transaction) ob.WithLastTxScanned(txHash) @@ -251,10 +283,14 @@ func (ob *Observer) setLastScannedTX(tx *ton.Transaction) { Msg("setLastScannedTX: WriteLastTxScannedToDB") } -func txLogFields(tx *ton.Transaction) map[string]any { +func txLogFields(tx *toncontracts.Transaction) map[string]any { return map[string]any{ - "inbound.ton.lt": tx.Lt, - "inbound.ton.hash": tx.Hash().Hex(), - "inbound.ton.block_id": tx.BlockID.BlockID.String(), + "transaction.hash": liteapi.TransactionToHashString(tx.Transaction), + "transaction.ton.lt": tx.Lt, + "transaction.ton.hash": tx.Hash().Hex(), + "transaction.ton.block_id": tx.BlockID.BlockID.String(), + "transaction.ton.is_inbound": tx.IsInbound(), + "transaction.ton.op_code": tx.Operation, + "transaction.ton.exit_code": tx.ExitCode, } } diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go index 281db44f79..953db3c744 100644 --- a/zetaclient/chains/ton/observer/inbound_test.go +++ b/zetaclient/chains/ton/observer/inbound_test.go @@ -40,7 +40,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.ErrorContains(t, err, "unable to ensure last scanned tx") @@ -66,7 +66,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.NoError(t, err) @@ -107,7 +107,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.NoError(t, err) @@ -147,7 +147,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.NoError(t, err) @@ -209,7 +209,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.NoError(t, err) @@ -245,6 +245,55 @@ func TestInbound(t *testing.T) { assert.Equal(t, uint64(blockInfo.MinRefMcSeqno), cctx.InboundBlockHeight) }) + // Yep, it's possible to have withdrawals here because we scroll through all gateway's txs + t.Run("Withdrawal", func(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + + // Given observer + ob, err := New(ts.baseObserver, ts.liteClient, gw) + require.NoError(t, err) + + lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + + // Given mocked lite client calls + withdrawal := toncontracts.Withdrawal{ + Recipient: ton.MustParseAccountID("EQB5A1PJBbnxwf0YrA_bgWKyfuIv8GywEcfIAXrs3oZyqc1_"), + Amount: toncontracts.Coins(5), + Seqno: 0, + } + + ts.sign(&withdrawal) + + withdrawalSigner, err := withdrawal.Signer() + require.NoError(t, err) + require.Equal(t, ob.TSS().EVMAddress().Hex(), withdrawalSigner.Hex()) + + withdrawalTX := sample.TONWithdrawal(t, gw.AccountID(), withdrawal) + txs := []ton.Transaction{withdrawalTX} + + ts. + OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + Once() + + // ACT + err = ob.observeGateway(ts.ctx) + + // ASSERT + assert.NoError(t, err) + + // Check that no votes were sent + require.Len(t, ts.votesBag, 0) + + // But an outbound tracker was created + require.Len(t, ts.trackerBag, 1) + + tracker := ts.trackerBag[0] + + assert.Equal(t, uint64(withdrawal.Seqno), tracker.nonce) + assert.Equal(t, liteapi.TransactionToHashString(withdrawalTX), tracker.hash) + }) + t.Run("Multiple transactions", func(t *testing.T) { // ARRANGE ts := newTestSuite(t) @@ -256,6 +305,13 @@ func TestInbound(t *testing.T) { lastScanned := ts.SetupLastScannedTX(gw.AccountID()) // Given several transactions + withdrawal := toncontracts.Withdrawal{ + Recipient: ton.MustParseAccountID("EQB5A1PJBbnxwf0YrA_bgWKyfuIv8GywEcfIAXrs3oZyqc1_"), + Amount: toncontracts.Coins(5), + Seqno: 1, + } + ts.sign(&withdrawal) + txs := []ton.Transaction{ // should be skipped sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{ @@ -279,6 +335,8 @@ func TestInbound(t *testing.T) { Amount: tonCoins(t, "3"), Recipient: sample.EthAddress(), }), + // a tracker should be added + sample.TONWithdrawal(t, gw.AccountID(), withdrawal), // should be skipped (invalid inbound/outbound messages) sample.TONTransaction(t, sample.TONTransactionProps{ Account: gw.AccountID(), @@ -297,7 +355,7 @@ func TestInbound(t *testing.T) { // ACT // Observe inbounds once - err = ob.observeInbound(ts.ctx) + err = ob.observeGateway(ts.ctx) // ASSERT assert.NoError(t, err) @@ -323,6 +381,13 @@ func TestInbound(t *testing.T) { assert.NoError(t, err) assert.Equal(t, lastTX.Lt, lastLT) assert.Equal(t, lastTX.Hash().Hex(), lastHash.Hex()) + + // Check that a tracker was added + assert.Len(t, ts.trackerBag, 1) + tracker := ts.trackerBag[0] + + assert.Equal(t, uint64(withdrawal.Seqno), tracker.nonce) + assert.Equal(t, liteapi.TransactionToHashString(txs[4]), tracker.hash) }) } diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go index e20742116a..41523d5759 100644 --- a/zetaclient/chains/ton/observer/observer.go +++ b/zetaclient/chains/ton/observer/observer.go @@ -2,16 +2,22 @@ package observer import ( "context" - "errors" + "time" + lru "github.com/hashicorp/golang-lru" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/tonkeeper/tongo/liteclient" "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" "github.com/zeta-chain/node/pkg/bg" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" - "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/pkg/ticker" "github.com/zeta-chain/node/zetaclient/chains/base" "github.com/zeta-chain/node/zetaclient/chains/interfaces" + zetaton "github.com/zeta-chain/node/zetaclient/chains/ton" + "github.com/zeta-chain/node/zetaclient/common" ) // Observer is a TON observer. @@ -20,15 +26,25 @@ type Observer struct { client LiteClient gateway *toncontracts.Gateway + + outbounds *lru.Cache } +var _ interfaces.ChainObserver = (*Observer)(nil) + +const outboundsCacheSize = 1024 + // LiteClient represents a TON client +// see https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl // //go:generate mockery --name LiteClient --filename ton_liteclient.go --case underscore --output ../../../testutils/mocks type LiteClient interface { + zetaton.ConfigGetter + GetMasterchainInfo(ctx context.Context) (liteclient.LiteServerMasterchainInfoC, error) GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error) - GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error) - GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error) + GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) ([]ton.Transaction, error) + GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*ton.Transaction, int, error) + GetTransaction(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) (ton.Transaction, error) } var _ interfaces.ChainObserver = (*Observer)(nil) @@ -44,37 +60,146 @@ func New(bo *base.Observer, client LiteClient, gateway *toncontracts.Gateway) (* return nil, errors.New("gateway is nil") } + outbounds, err := lru.New(outboundsCacheSize) + if err != nil { + return nil, err + } + bo.LoadLastTxScanned() return &Observer{ - Observer: bo, - client: client, - gateway: gateway, + Observer: bo, + client: client, + gateway: gateway, + outbounds: outbounds, }, nil } // Start starts the observer. This method is NOT blocking. +// Note that each `watch*` method has a ticker that will stop as soon as +// baseObserver.Stop() was called (ticker.WithStopChan) func (ob *Observer) Start(ctx context.Context) { if ok := ob.Observer.Start(); !ok { - ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId) + ob.Logger().Chain.Info().Msg("observer is already started") return } - ob.Logger().Chain.Info().Msgf("observer is starting for chain %d", ob.Chain().ChainId) + ob.Logger().Chain.Info().Msg("observer is starting") - // Note that each `watch*` method has a ticker that will stop as soon as - // baseObserver.Stop() was called (ticker.WithStopChan) - - // watch for incoming txs and post votes to zetacore - bg.Work(ctx, ob.watchInbound, bg.WithName("WatchInbound"), bg.WithLogger(ob.Logger().Inbound)) + start := func(job func(ctx context.Context) error, name string, log zerolog.Logger) { + bg.Work(ctx, job, bg.WithName(name), bg.WithLogger(log)) + } // TODO: watchInboundTracker // https://github.com/zeta-chain/node/issues/2935 - // TODO: outbounds/withdrawals: (watchOutbound, watchGasPrice, watchRPCStatus) - // https://github.com/zeta-chain/node/issues/2807 + start(ob.watchInbound, "WatchInbound", ob.Logger().Inbound) + start(ob.watchOutbound, "WatchOutbound", ob.Logger().Outbound) + start(ob.watchGasPrice, "WatchGasPrice", ob.Logger().GasPrice) + start(ob.watchRPCStatus, "WatchRPCStatus", ob.Logger().Chain) +} + +// watchGasPrice observes TON gas price and votes it to Zetacore. +func (ob *Observer) watchGasPrice(ctx context.Context) error { + task := func(ctx context.Context, t *ticker.Ticker) error { + if err := ob.postGasPrice(ctx); err != nil { + ob.Logger().GasPrice.Err(err).Msg("WatchGasPrice: postGasPrice error") + } + + newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().GasPriceTicker) + t.SetInterval(newInterval) + + return nil + } + + ob.Logger().GasPrice.Info().Msg("WatchGasPrice started") + + return ticker.Run( + ctx, + ticker.DurationFromUint64Seconds(ob.ChainParams().GasPriceTicker), + task, + ticker.WithStopChan(ob.StopChannel()), + ticker.WithLogger(ob.Logger().GasPrice, "WatchGasPrice"), + ) } -func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossChainTx) (bool, error) { - return false, errors.New("not implemented") +// postGasPrice fetches on-chain gas config and reports it to Zetacore. +func (ob *Observer) postGasPrice(ctx context.Context) error { + cfg, err := zetaton.FetchGasConfig(ctx, ob.client) + if err != nil { + return errors.Wrap(err, "failed to fetch gas config") + } + + gasPrice, err := zetaton.ParseGasPrice(cfg) + if err != nil { + return errors.Wrap(err, "failed to parse gas price") + } + + blockID, err := ob.getLatestMasterchainBlock(ctx) + if err != nil { + return errors.Wrap(err, "failed to get latest masterchain block") + } + + // There's no concept of priority fee in TON + const priorityFee = 0 + + _, errVote := ob. + ZetacoreClient(). + PostVoteGasPrice(ctx, ob.Chain(), gasPrice, priorityFee, uint64(blockID.Seqno)) + + return errVote +} + +// watchRPCStatus observes TON RPC status. +func (ob *Observer) watchRPCStatus(ctx context.Context) error { + task := func(ctx context.Context, _ *ticker.Ticker) error { + if err := ob.checkRPCStatus(ctx); err != nil { + ob.Logger().Chain.Err(err).Msg("checkRPCStatus error") + } + + return nil + } + + return ticker.Run( + ctx, + common.RPCStatusCheckInterval, + task, + ticker.WithStopChan(ob.StopChannel()), + ticker.WithLogger(ob.Logger().Chain, "WatchRPCStatus"), + ) +} + +// checkRPCStatus checks TON RPC status and alerts if necessary. +func (ob *Observer) checkRPCStatus(ctx context.Context) error { + blockID, err := ob.getLatestMasterchainBlock(ctx) + if err != nil { + return errors.Wrap(err, "failed to get latest masterchain block") + } + + block, err := ob.client.GetBlockHeader(ctx, blockID, 0) + if err != nil { + return errors.Wrap(err, "failed to get masterchain block header") + } + + if block.NotMaster { + return errors.Errorf("block %q is not a master block", blockID.BlockID.String()) + } + + blockTime := time.Unix(int64(block.GenUtime), 0).UTC() + + // will be overridden by chain config + const defaultAlertLatency = 30 * time.Second + + ob.AlertOnRPCLatency(blockTime, defaultAlertLatency) + + return nil +} + +func (ob *Observer) getLatestMasterchainBlock(ctx context.Context) (ton.BlockIDExt, error) { + mc, err := ob.client.GetMasterchainInfo(ctx) + if err != nil { + return ton.BlockIDExt{}, errors.Wrap(err, "failed to get masterchain info") + } + + return mc.Last.ToBlockIdExt(), nil } diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go index 38c032eb4a..502a269267 100644 --- a/zetaclient/chains/ton/observer/observer_test.go +++ b/zetaclient/chains/ton/observer/observer_test.go @@ -6,6 +6,7 @@ import ( "testing" "cosmossdk.io/math" + eth "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -14,7 +15,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" "github.com/zeta-chain/node/testutil/sample" - cctxtypes "github.com/zeta-chain/node/x/crosschain/types" + cc "github.com/zeta-chain/node/x/crosschain/types" observertypes "github.com/zeta-chain/node/x/observer/types" "github.com/zeta-chain/node/zetaclient/chains/base" "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" @@ -38,7 +39,13 @@ type testSuite struct { baseObserver *base.Observer - votesBag []*cctxtypes.MsgVoteInbound + votesBag []*cc.MsgVoteInbound + trackerBag []testTracker +} + +type testTracker struct { + nonce uint64 + hash string } func newTestSuite(t *testing.T) *testSuite { @@ -50,7 +57,7 @@ func newTestSuite(t *testing.T) *testSuite { liteClient = mocks.NewLiteClient(t) - tss = mocks.NewTSSAthens3() + tss = mocks.NewGeneratedTSS(t, chain) zetacore = mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}) testLogger = zerolog.New(zerolog.NewTestWriter(t)) @@ -95,6 +102,7 @@ func newTestSuite(t *testing.T) *testSuite { ts.zetacore.On("Chain").Return(chain).Maybe() setupVotesBag(ts) + setupTrackersBag(ts) return ts } @@ -119,6 +127,18 @@ func (ts *testSuite) OnGetFirstTransaction(acc ton.AccountID, tx *ton.Transactio Return(tx, scanned, err) } +func (ts *testSuite) MockGetTransaction(acc ton.AccountID, tx ton.Transaction) *mock.Call { + return ts.liteClient. + On("GetTransaction", mock.Anything, acc, tx.Lt, ton.Bits256(tx.Hash())). + Return(tx, nil) +} + +func (ts *testSuite) MockCCTXByNonce(cctx *cc.CrossChainTx) *mock.Call { + nonce := cctx.GetCurrentOutboundParam().TssNonce + + return ts.zetacore.On("GetCctxByNonce", ts.ctx, ts.chain.ChainId, nonce).Return(cctx, nil) +} + func (ts *testSuite) OnGetTransactionsSince( acc ton.AccountID, lt uint64, @@ -131,6 +151,12 @@ func (ts *testSuite) OnGetTransactionsSince( Return(txs, err) } +func (ts *testSuite) OnGetAllOutboundTrackerByChain(trackers []cc.OutboundTracker) *mock.Call { + return ts.zetacore. + On("GetAllOutboundTrackerByChain", mock.Anything, ts.chain.ChainId, mock.Anything). + Return(trackers, nil) +} + func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call { // let's pretend that block's masterchain ref has the same seqno blockInfo := tlb.BlockInfo{ @@ -142,6 +168,27 @@ func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call { Return(blockInfo, nil) } +type signable interface { + Hash() ([32]byte, error) + SetSignature([65]byte) + Signer() (eth.Address, error) +} + +func (ts *testSuite) sign(msg signable) { + hash, err := msg.Hash() + require.NoError(ts.t, err) + + sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0, "") + require.NoError(ts.t, err) + + msg.SetSignature(sig) + + // double check + evmSigner, err := msg.Signer() + require.NoError(ts.t, err) + require.Equal(ts.t, ts.tss.EVMAddress().String(), evmSigner.String()) +} + // parses string to TON func tonCoins(t *testing.T, raw string) math.Uint { t.Helper() @@ -159,7 +206,7 @@ func tonCoins(t *testing.T, raw string) math.Uint { func setupVotesBag(ts *testSuite) { catcher := func(args mock.Arguments) { vote := args.Get(3) - cctx, ok := vote.(*cctxtypes.MsgVoteInbound) + cctx, ok := vote.(*cc.MsgVoteInbound) require.True(ts.t, ok, "unexpected cctx type") ts.votesBag = append(ts.votesBag, cctx) @@ -170,3 +217,26 @@ func setupVotesBag(ts *testSuite) { Run(catcher). Return("", "", nil) // zeta hash, ballot index, error } + +func setupTrackersBag(ts *testSuite) { + catcher := func(args mock.Arguments) { + require.Equal(ts.t, ts.chain.ChainId, args.Get(1).(int64)) + nonce := args.Get(2).(uint64) + txHash := args.Get(3).(string) + + ts.t.Logf("Adding outbound tracker: nonce=%d, hash=%s", nonce, txHash) + + ts.trackerBag = append(ts.trackerBag, testTracker{nonce, txHash}) + } + + ts.zetacore.On( + "AddOutboundTracker", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Maybe().Run(catcher).Return("", nil) +} diff --git a/zetaclient/chains/ton/observer/outbound.go b/zetaclient/chains/ton/observer/outbound.go new file mode 100644 index 0000000000..b4a466bcf2 --- /dev/null +++ b/zetaclient/chains/ton/observer/outbound.go @@ -0,0 +1,323 @@ +package observer + +import ( + "context" + + "cosmossdk.io/math" + eth "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/rs/zerolog" + + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/pkg/ticker" + cc "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" + "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" + zctx "github.com/zeta-chain/node/zetaclient/context" + gasconst "github.com/zeta-chain/node/zetaclient/zetacore" +) + +type outbound struct { + tx *toncontracts.Transaction + receiveStatus chains.ReceiveStatus + nonce uint64 +} + +// VoteOutboundIfConfirmed checks outbound status and returns (continueKeysign, error) +func (ob *Observer) VoteOutboundIfConfirmed(ctx context.Context, cctx *cc.CrossChainTx) (bool, error) { + nonce := cctx.GetCurrentOutboundParam().TssNonce + + outboundRes, exists := ob.getOutboundByNonce(nonce) + if !exists { + return true, nil + } + + withdrawal, err := outboundRes.tx.Withdrawal() + if err != nil { + return false, errors.Wrap(err, "unable to get withdrawal") + } + + // TODO: Add compliance check + // https://github.com/zeta-chain/node/issues/2916 + + txHash := liteapi.TransactionToHashString(outboundRes.tx.Transaction) + if err = ob.postVoteOutbound(ctx, cctx, withdrawal, txHash, outboundRes.receiveStatus); err != nil { + return false, errors.Wrap(err, "unable to post vote") + } + + return false, nil +} + +// watchOutbound watches outbound transactions and caches them in-memory so they can be used later in +// VoteOutboundIfConfirmed +func (ob *Observer) watchOutbound(ctx context.Context) error { + app, err := zctx.FromContext(ctx) + if err != nil { + return err + } + + var ( + initialInterval = ticker.DurationFromUint64Seconds(ob.ChainParams().OutboundTicker) + sampledLogger = ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10}) + ) + + task := func(ctx context.Context, t *ticker.Ticker) error { + if !app.IsOutboundObservationEnabled() { + sampledLogger.Info().Msg("WatchOutbound: outbound observation is disabled") + return nil + } + + if err := ob.observeOutboundTrackers(ctx); err != nil { + ob.Logger().Outbound.Err(err).Msg("WatchOutbound: observeOutboundTrackers error") + } + + newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().OutboundTicker) + t.SetInterval(newInterval) + + return nil + } + + return ticker.Run( + ctx, + initialInterval, + task, + ticker.WithStopChan(ob.StopChannel()), + ticker.WithLogger(ob.Logger().Outbound, "WatchOutbound"), + ) +} + +// observeOutboundTrackers pulls outbounds trackers from zetacore, +// fetches txs from TON and stores them in memory for further use. +func (ob *Observer) observeOutboundTrackers(ctx context.Context) error { + var ( + chainID = ob.Chain().ChainId + zetacore = ob.ZetacoreClient() + ) + + trackers, err := zetacore.GetAllOutboundTrackerByChain(ctx, chainID, interfaces.Ascending) + if err != nil { + return errors.Wrap(err, "unable to get outbound trackers") + } + + for _, tracker := range trackers { + nonce := tracker.Nonce + + // If outbound is already in memory, skip. + if _, ok := ob.getOutboundByNonce(nonce); ok { + continue + } + + // Let's not block other cctxs from being processed + cctx, err := zetacore.GetCctxByNonce(ctx, chainID, nonce) + if err != nil { + ob.Logger().Outbound. + Error().Err(err). + Uint64("outbound.nonce", nonce). + Msg("Unable to get cctx by nonce") + + continue + } + + for _, txHash := range tracker.HashList { + if err := ob.processOutboundTracker(ctx, cctx, txHash.TxHash); err != nil { + ob.Logger().Outbound. + Error().Err(err). + Uint64("outbound.nonce", nonce). + Str("outbound.hash", txHash.TxHash). + Msg("Unable to check transaction by nonce") + } + } + } + + return nil +} + +// processOutboundTracker checks TON tx and stores it in memory for further processing +// by VoteOutboundIfConfirmed. +func (ob *Observer) processOutboundTracker(ctx context.Context, cctx *cc.CrossChainTx, txHash string) error { + if cctx.InboundParams.CoinType != coin.CoinType_Gas { + return errors.New("only gas cctxs are supported") + } + + lt, hash, err := liteapi.TransactionHashFromString(txHash) + if err != nil { + return errors.Wrap(err, "unable to parse tx hash") + } + + rawTX, err := ob.client.GetTransaction(ctx, ob.gateway.AccountID(), lt, hash) + if err != nil { + return errors.Wrap(err, "unable to get transaction form liteapi") + } + + tx, err := ob.gateway.ParseTransaction(rawTX) + if err != nil { + return errors.Wrap(err, "unable to parse transaction") + } + + receiveStatus, err := ob.determineReceiveStatus(tx) + if err != nil { + return errors.Wrap(err, "unable to determine outbound outcome") + } + + // TODO: Add compliance check + // https://github.com/zeta-chain/node/issues/2916 + + nonce := cctx.GetCurrentOutboundParam().TssNonce + ob.setOutboundByNonce(outbound{tx, receiveStatus, nonce}) + + return nil +} + +func (ob *Observer) determineReceiveStatus(tx *toncontracts.Transaction) (chains.ReceiveStatus, error) { + _, evmSigner, err := extractWithdrawal(tx) + switch { + case err != nil: + return 0, err + case evmSigner != ob.TSS().EVMAddress(): + return 0, errors.New("withdrawal signer is not TSS") + case !tx.IsSuccess(): + return chains.ReceiveStatus_failed, nil + default: + return chains.ReceiveStatus_success, nil + } +} + +// addOutboundTracker publishes outbound tracker to Zetacore. +// In most cases will be a noop because the tracker is already published by the signer. +// See Signer{}.trackOutbound(...) for more details. +func (ob *Observer) addOutboundTracker(ctx context.Context, tx *toncontracts.Transaction) error { + w, evmSigner, err := extractWithdrawal(tx) + switch { + case err != nil: + return err + case evmSigner != ob.TSS().EVMAddress(): + ob.Logger().Inbound.Warn(). + Fields(txLogFields(tx)). + Str("transaction.ton.signer", evmSigner.String()). + Msg("observeGateway: addOutboundTracker: withdrawal signer is not TSS. Skipping") + + return nil + } + + var ( + chainID = ob.Chain().ChainId + nonce = uint64(w.Seqno) + hash = liteapi.TransactionToHashString(tx.Transaction) + ) + + // note it has a check for noop + _, err = ob. + ZetacoreClient(). + AddOutboundTracker(ctx, chainID, nonce, hash, nil, "", 0) + + return err +} + +// return withdrawal and tx signer +func extractWithdrawal(tx *toncontracts.Transaction) (toncontracts.Withdrawal, eth.Address, error) { + w, err := tx.Withdrawal() + if err != nil { + return toncontracts.Withdrawal{}, eth.Address{}, errors.Wrap(err, "not a withdrawal") + } + + s, err := w.Signer() + if err != nil { + return toncontracts.Withdrawal{}, eth.Address{}, errors.Wrap(err, "unable to get signer") + } + + return w, s, nil +} + +// getOutboundByNonce returns outbound by nonce +func (ob *Observer) getOutboundByNonce(nonce uint64) (outbound, bool) { + v, ok := ob.outbounds.Get(nonce) + if !ok { + return outbound{}, false + } + + return v.(outbound), true +} + +// setOutboundByNonce stores outbound by nonce +func (ob *Observer) setOutboundByNonce(o outbound) { + ob.outbounds.Add(o.nonce, o) +} + +func (ob *Observer) postVoteOutbound( + ctx context.Context, + cctx *cc.CrossChainTx, + w toncontracts.Withdrawal, + txHash string, + status chains.ReceiveStatus, +) error { + // I. Gas + // TON implements a different tx fee model. Basically, each operation in our Gateway has a + // tx_fee(operation) which is based on hard-coded gas values per operation + // multiplied by the current gas fees on-chain. Each withdrawal tx takes gas directly + // from the Gateway i.e. gw pays tx fees for itself. + // + // - Gas price is stores in zetacore thanks to Observer.postGasPrice() + // - Gas limit should be hardcoded in TON ZRC-20 + // + // II. Block height + // TON doesn't sequential block height because different txs might end up in different shard chains + // tlb.BlockID is essentially a workchain+shard+seqno tuple. We can't use it as a block height. Thus let's use 0. + // Note that for the sake of gas tracking, we use masterchain block height (not applicable here). + const ( + outboundGasUsed = 0 + outboundGasPrice = 0 + outboundGasLimit = 0 + outboundBlockHeight = 0 + ) + + var ( + chainID = ob.Chain().ChainId + nonce = cctx.GetCurrentOutboundParam().TssNonce + signerAddress = ob.ZetacoreClient().GetKeys().GetOperatorAddress() + coinType = cctx.InboundParams.CoinType + ) + + msg := cc.NewMsgVoteOutbound( + signerAddress.String(), + cctx.Index, + txHash, + outboundBlockHeight, + outboundGasUsed, + math.NewInt(outboundGasPrice), + outboundGasLimit, + w.Amount, + status, + chainID, + nonce, + coinType, + ) + + const gasLimit = gasconst.PostVoteOutboundGasLimit + + var retryGasLimit uint64 + if msg.Status == chains.ReceiveStatus_failed { + retryGasLimit = gasconst.PostVoteOutboundRevertGasLimit + } + + log := ob.Logger().Outbound.With(). + Uint64("outbound.nonce", nonce). + Str("outbound.outbound_tx_hash", txHash). + Logger() + + zetaTxHash, ballot, err := ob.ZetacoreClient().PostVoteOutbound(ctx, gasLimit, retryGasLimit, msg) + if err != nil { + log.Error().Err(err).Msg("PostVoteOutbound: error posting vote") + return err + } + + if zetaTxHash != "" { + log.Info(). + Str("outbound.vote_tx_hash", zetaTxHash). + Str("outbound.ballot_id", ballot). + Msg("PostVoteOutbound: posted vote") + } + + return nil +} diff --git a/zetaclient/chains/ton/observer/outbound_test.go b/zetaclient/chains/ton/observer/outbound_test.go new file mode 100644 index 0000000000..4358c353b4 --- /dev/null +++ b/zetaclient/chains/ton/observer/outbound_test.go @@ -0,0 +1,80 @@ +package observer + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/ton" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/testutil/sample" + cc "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" +) + +func TestOutbound(t *testing.T) { + gw := toncontracts.NewGateway( + ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b"), + ) + + t.Run("observeOutboundTrackers", func(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + + ob, err := New(ts.baseObserver, ts.liteClient, gw) + require.NoError(t, err) + + // Given withdrawal + withdrawal := toncontracts.Withdrawal{ + Recipient: ton.MustParseAccountID("0:552f6db5da0cae7f0b3ab4ab58d85927f6beb962cda426a6a6ee751c82cead1f"), + Amount: toncontracts.Coins(2), + Seqno: 3, + } + ts.sign(&withdrawal) + + nonce := uint64(withdrawal.Seqno) + + // Given TON tx + withdrawalTX := sample.TONWithdrawal(t, gw.AccountID(), withdrawal) + + ts.MockGetTransaction(gw.AccountID(), withdrawalTX) + + // Given outbound tracker + tracker := cc.OutboundTracker{ + Index: "index123", + ChainId: ts.chain.ChainId, + Nonce: nonce, + HashList: []*cc.TxHash{{TxHash: liteapi.TransactionToHashString(withdrawalTX)}}, + } + + ts.OnGetAllOutboundTrackerByChain([]cc.OutboundTracker{tracker}) + + // Given cctx + cctx := sample.CrossChainTx(t, "index456") + cctx.InboundParams.CoinType = coin.CoinType_Gas + cctx.GetCurrentOutboundParam().TssNonce = nonce + + ts.MockCCTXByNonce(cctx) + + // ACT + err = ob.observeOutboundTrackers(ts.ctx) + + // ASSERT + require.NoError(t, err) + + // Check that tx exists in outbounds + res, exists := ob.getOutboundByNonce(nonce) + assert.True(t, exists) + + assert.Equal(t, nonce, res.nonce) + assert.Equal(t, chains.ReceiveStatus_success, res.receiveStatus) + assert.Equal(t, true, res.tx.IsSuccess()) + assert.Equal(t, int32(0), res.tx.ExitCode) + + w2, err := res.tx.Withdrawal() + assert.NoError(t, err) + assert.Equal(t, withdrawal, w2) + }) +} diff --git a/zetaclient/chains/ton/signer/signer.go b/zetaclient/chains/ton/signer/signer.go new file mode 100644 index 0000000000..7542fb37f6 --- /dev/null +++ b/zetaclient/chains/ton/signer/signer.go @@ -0,0 +1,242 @@ +package signer + +import ( + "context" + + ethcommon "github.com/ethereum/go-ethereum/common" + lru "github.com/hashicorp/golang-lru" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + + "github.com/zeta-chain/node/pkg/coin" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + cc "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" + "github.com/zeta-chain/node/zetaclient/outboundprocessor" +) + +// LiteClient represents a TON client +// see https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl +// +//go:generate mockery --name LiteClient --structname SignerLiteClient --filename ton_signerliteclient.go --case underscore --output ../../../testutils/mocks +type LiteClient interface { + GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) ([]ton.Transaction, error) + GetAccountState(ctx context.Context, accountID ton.AccountID) (tlb.ShardAccount, error) + SendMessage(ctx context.Context, payload []byte) (uint32, error) +} + +// Signer represents TON signer. +type Signer struct { + *base.Signer + client LiteClient + gateway *toncontracts.Gateway + signaturesCache *lru.Cache +} + +// Signable represents a message that can be signed. +type Signable interface { + Hash() ([32]byte, error) + SetSignature(sig [65]byte) +} + +// Outcome possible outbound processing outcomes. +type Outcome string + +const ( + Invalid Outcome = "invalid" + Fail Outcome = "fail" + Success Outcome = "success" +) + +const signaturesHashSize = 1024 + +var _ interfaces.ChainSigner = (*Signer)(nil) + +// New Signer constructor. +func New(baseSigner *base.Signer, client LiteClient, gateway *toncontracts.Gateway) *Signer { + sigCache, _ := lru.New(signaturesHashSize) + + return &Signer{ + Signer: baseSigner, + client: client, + gateway: gateway, + signaturesCache: sigCache, + } +} + +// TryProcessOutbound tries to process outbound cctx. +// Note that this API signature will be refactored in orchestrator V2 +func (s *Signer) TryProcessOutbound( + ctx context.Context, + cctx *cc.CrossChainTx, + proc *outboundprocessor.Processor, + outboundID string, + _ interfaces.ChainObserver, + zetacore interfaces.ZetacoreClient, + zetaBlockHeight uint64, +) { + proc.StartTryProcess(outboundID) + + defer func() { + proc.EndTryProcess(outboundID) + }() + + outcome, err := s.ProcessOutbound(ctx, cctx, zetacore, zetaBlockHeight) + if err != nil { + s.Logger().Std.Error(). + Err(err). + Str("outbound.id", outboundID). + Uint64("outbound.nonce", cctx.GetCurrentOutboundParam().TssNonce). + Str("outbound.outcome", string(outcome)). + Msg("Unable to ProcessOutbound") + } +} + +// ProcessOutbound signs and broadcasts an outbound cross-chain transaction. +func (s *Signer) ProcessOutbound( + ctx context.Context, + cctx *cc.CrossChainTx, + zetacore interfaces.ZetacoreClient, + zetaHeight uint64, +) (Outcome, error) { + // TODO: note that *InboundParams* are use used on purpose due to legacy reasons. + // https://github.com/zeta-chain/node/issues/1949 + if cctx.InboundParams.CoinType != coin.CoinType_Gas { + return Invalid, errors.New("only gas coin outbounds are supported") + } + + params := cctx.GetCurrentOutboundParam() + + // TODO: add compliance check + // https://github.com/zeta-chain/node/issues/2916 + + receiver, err := ton.ParseAccountID(params.Receiver) + if err != nil { + return Invalid, errors.Wrapf(err, "unable to parse recipient %q", params.Receiver) + } + + withdrawal := &toncontracts.Withdrawal{ + Recipient: receiver, + Amount: params.Amount, + // #nosec G115 always in range + Seqno: uint32(params.TssNonce), + } + + lf := map[string]any{ + "outbound.recipient": withdrawal.Recipient.ToRaw(), + "outbound.amount": withdrawal.Amount.Uint64(), + "outbound.nonce": withdrawal.Seqno, + } + + s.Logger().Std.Info().Fields(lf).Msg("Signing withdrawal") + + if err = s.SignMessage(ctx, withdrawal, zetaHeight, params.TssNonce); err != nil { + return Fail, errors.Wrap(err, "unable to sign withdrawal message") + } + + gwState, err := s.client.GetAccountState(ctx, s.gateway.AccountID()) + if err != nil { + return Fail, errors.Wrap(err, "unable to get gateway state") + } + + // Publishes signed message to Gateway + // Note that max(tx fee) is hardcoded in the contract. + // + // Example: If a cctx has amount of 5 TON, the recipient will receive 5 TON, + // and gateway's balance will be decreased by 5 TON + txFees. + exitCode, err := s.gateway.SendExternalMessage(ctx, s.client, withdrawal) + switch { + case err != nil: + return Fail, errors.Wrap(err, "unable to send external message") + case exitCode != 0: + // Might happen if msg's nonce is too high; retry later. + lf["outbound.exit_code"] = exitCode + lf["outbound.outcome"] = string(Invalid) + s.Logger().Std.Info().Fields(lf).Msg("Unable to send external message") + + return Invalid, nil + } + + // it's okay to run this in the same goroutine + // because TryProcessOutbound method should be called in a goroutine + if err = s.trackOutbound(ctx, zetacore, withdrawal, gwState); err != nil { + return Fail, errors.Wrap(err, "unable to track outbound") + } + + return Success, nil +} + +// SignMessage signs TON external message using TSS +func (s *Signer) SignMessage(ctx context.Context, msg Signable, zetaHeight, nonce uint64) error { + hash, err := msg.Hash() + if err != nil { + return errors.Wrap(err, "unable to hash message") + } + + // cache hit + if sig, ok := s.getSignature(hash); ok { + msg.SetSignature(sig) + return nil + } + + chainID := s.Chain().ChainId + + // sig = [65]byte {R, S, V (recovery ID)} + sig, err := s.TSS().Sign(ctx, hash[:], zetaHeight, nonce, chainID, "") + if err != nil { + return errors.Wrap(err, "unable to sign the message") + } + + msg.SetSignature(sig) + s.setSignature(hash, sig) + + return nil +} + +// because signed msg might fail due to high nonce, +// we need to make sure that signature is cached to avoid redundant TSS calls +func (s *Signer) getSignature(hash [32]byte) ([65]byte, bool) { + sig, ok := s.signaturesCache.Get(hash) + if !ok { + return [65]byte{}, false + } + + return sig.([65]byte), true +} + +// caches signature +func (s *Signer) setSignature(hash [32]byte, sig [65]byte) { + s.signaturesCache.Add(hash, sig) +} + +// GetGatewayAddress returns gateway address as raw TON address "0:ABC..." +func (s *Signer) GetGatewayAddress() string { + return s.gateway.AccountID().ToRaw() +} + +// SetGatewayAddress sets gateway address. Has a check for noop. +func (s *Signer) SetGatewayAddress(addr string) { + // noop + if s.gateway.AccountID().ToRaw() == addr { + return + } + + acc, err := ton.ParseAccountID(addr) + if err != nil { + s.Logger().Std.Error().Err(err).Str("addr", addr).Msg("unable to parse gateway address") + return + } + + s.Lock() + s.gateway = toncontracts.NewGateway(acc) + s.Unlock() +} + +// not used + +func (s *Signer) GetZetaConnectorAddress() (_ ethcommon.Address) { return } +func (s *Signer) GetERC20CustodyAddress() (_ ethcommon.Address) { return } +func (s *Signer) SetZetaConnectorAddress(_ ethcommon.Address) {} +func (s *Signer) SetERC20CustodyAddress(_ ethcommon.Address) {} diff --git a/zetaclient/chains/ton/signer/signer_test.go b/zetaclient/chains/ton/signer/signer_test.go new file mode 100644 index 0000000000..9ac85f281a --- /dev/null +++ b/zetaclient/chains/ton/signer/signer_test.go @@ -0,0 +1,233 @@ +package signer + +import ( + "context" + "encoding/hex" + "strconv" + "testing" + + "cosmossdk.io/math" + "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/testutil/sample" + cc "github.com/zeta-chain/node/x/crosschain/types" + observertypes "github.com/zeta-chain/node/x/observer/types" + "github.com/zeta-chain/node/zetaclient/chains/base" + "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" + "github.com/zeta-chain/node/zetaclient/keys" + "github.com/zeta-chain/node/zetaclient/outboundprocessor" + "github.com/zeta-chain/node/zetaclient/testutils/mocks" +) + +func TestSigner(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + + // Given TON signer + signer := New(ts.baseSigner, ts.liteClient, ts.gw) + + // Given a sample TON receiver + receiver := ton.MustParseAccountID("0QAyaVdkvWSuax8luWhDXY_0X9Am1ASWlJz4OI7M-jqcM5wK") + + const ( + zetaHeight = 123 + outboundID = "abc123" + nonce = 2 + ) + + amount := tonCoins(t, "5") + + // Given CCTX + cctx := sample.CrossChainTx(t, "123") + cctx.InboundParams.CoinType = coin.CoinType_Gas + cctx.OutboundParams = []*cc.OutboundParams{{ + Receiver: receiver.ToRaw(), + ReceiverChainId: ts.chain.ChainId, + CoinType: coin.CoinType_Gas, + Amount: amount, + TssNonce: nonce, + }} + + // Given expected withdrawal + withdrawal := toncontracts.Withdrawal{ + Recipient: receiver, + Amount: amount, + Seqno: nonce, + } + + ts.Sign(&withdrawal) + + // Given expected liteapi calls + lt, hash := uint64(400), decodeHash(t, "df8a01053f50a74503dffe6802f357bf0e665bd1f3d082faccfebdea93cddfeb") + ts.OnGetAccountState(ts.gw.AccountID(), tlb.ShardAccount{LastTransLt: lt, LastTransHash: hash}) + + ts.OnSendMessage(0, nil) + + withdrawalTX := sample.TONWithdrawal(t, ts.gw.AccountID(), withdrawal) + ts.OnGetTransactionsSince(ts.gw.AccountID(), lt, ton.Bits256(hash), []ton.Transaction{withdrawalTX}, nil) + + // ACT + signer.TryProcessOutbound(ts.ctx, cctx, ts.proc, outboundID, nil, ts.zetacore, zetaHeight) + + // ASSERT + // Make sure signer send the tx the chain AND published the outbound tracker + require.Len(t, ts.trackerBag, 1) + + tracker := ts.trackerBag[0] + + require.Equal(t, uint64(nonce), tracker.nonce) + require.Equal(t, liteapi.TransactionToHashString(withdrawalTX), tracker.hash) +} + +type testSuite struct { + ctx context.Context + t *testing.T + + chain chains.Chain + chainParams *observertypes.ChainParams + + liteClient *mocks.SignerLiteClient + + zetacore *mocks.ZetacoreClient + tss *mocks.TSS + + gw *toncontracts.Gateway + baseSigner *base.Signer + proc *outboundprocessor.Processor + + trackerBag []testTracker +} + +type testTracker struct { + nonce uint64 + hash string +} + +func newTestSuite(t *testing.T) *testSuite { + var ( + ctx = context.Background() + + chain = chains.TONTestnet + chainParams = sample.ChainParams(chain.ChainId) + + liteClient = mocks.NewSignerLiteClient(t) + + tss = mocks.NewTSSAthens3() + zetacore = mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{}) + + testLogger = zerolog.New(zerolog.NewTestWriter(t)) + logger = base.Logger{Std: testLogger, Compliance: testLogger} + + gwAccountID = ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b") + ) + + ts := &testSuite{ + ctx: ctx, + t: t, + + chain: chain, + chainParams: chainParams, + + liteClient: liteClient, + + zetacore: zetacore, + tss: tss, + + proc: outboundprocessor.NewProcessor(logger.Std), + gw: toncontracts.NewGateway(gwAccountID), + baseSigner: base.NewSigner(chain, tss, nil, logger), + } + + // Setup mocks + ts.zetacore.On("Chain").Return(chain).Maybe() + + setupTrackersBag(ts) + + return ts +} + +func (ts *testSuite) OnGetAccountState(acc ton.AccountID, state tlb.ShardAccount) *mock.Call { + return ts.liteClient.On("GetAccountState", mock.Anything, acc).Return(state, nil) +} + +func (ts *testSuite) OnSendMessage(id uint32, err error) *mock.Call { + return ts.liteClient.On("SendMessage", mock.Anything, mock.Anything).Return(id, err) +} + +func (ts *testSuite) OnGetTransactionsSince( + acc ton.AccountID, + lt uint64, + hash ton.Bits256, + txs []ton.Transaction, + err error, +) *mock.Call { + return ts.liteClient. + On("GetTransactionsSince", mock.Anything, acc, lt, hash). + Return(txs, err) +} + +func (ts *testSuite) Sign(msg Signable) { + hash, err := msg.Hash() + require.NoError(ts.t, err) + + sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0, "") + require.NoError(ts.t, err) + + msg.SetSignature(sig) +} + +// parses string to TON +func tonCoins(t *testing.T, raw string) math.Uint { + t.Helper() + + const oneTON = 1_000_000_000 + + f, err := strconv.ParseFloat(raw, 64) + require.NoError(t, err) + + f *= oneTON + + return math.NewUint(uint64(f)) +} + +func decodeHash(t *testing.T, raw string) tlb.Bits256 { + t.Helper() + + h, err := hex.DecodeString(raw) + require.NoError(t, err) + + var hash tlb.Bits256 + + copy(hash[:], h) + + return hash +} + +func setupTrackersBag(ts *testSuite) { + catcher := func(args mock.Arguments) { + require.Equal(ts.t, ts.chain.ChainId, args.Get(1).(int64)) + nonce := args.Get(2).(uint64) + txHash := args.Get(3).(string) + + ts.t.Logf("Adding outbound tracker: nonce=%d, hash=%s", nonce, txHash) + + ts.trackerBag = append(ts.trackerBag, testTracker{nonce, txHash}) + } + + ts.zetacore.On( + "AddOutboundTracker", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Maybe().Run(catcher).Return("", nil) +} diff --git a/zetaclient/chains/ton/signer/signer_tracker.go b/zetaclient/chains/ton/signer/signer_tracker.go new file mode 100644 index 0000000000..065d0f8204 --- /dev/null +++ b/zetaclient/chains/ton/signer/signer_tracker.go @@ -0,0 +1,92 @@ +package signer + +import ( + "context" + "time" + + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" + "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" +) + +// trackOutbound tracks sent external message and records it as outboundTracker. +// Explanation: +// Due to TON's nature, it's not possible to get tx hash before it's confirmed on-chain, +// So we need to poll from the latest account state (prevState) up to the most recent tx +// and search for desired tx hash. After it's found, we can record it as outboundTracker. +// +// Note that another zetaclient observers that scrolls Gateway's txs can publish this tracker concurrently. +func (s *Signer) trackOutbound( + ctx context.Context, + zetacore interfaces.ZetacoreClient, + w *toncontracts.Withdrawal, + prevState tlb.ShardAccount, +) error { + const ( + timeout = 60 * time.Second + tick = time.Second + ) + + var ( + start = time.Now() + chainID = s.Chain().ChainId + + acc = s.gateway.AccountID() + lt = prevState.LastTransLt + hash = ton.Bits256(prevState.LastTransHash) + nonce = uint64(w.Seqno) + + filter = withdrawalFilter(w) + ) + + for time.Since(start) <= timeout { + txs, err := s.client.GetTransactionsSince(ctx, acc, lt, hash) + if err != nil { + return errors.Wrapf(err, "unable to get transactions (lt %d, hash %s)", lt, hash.Hex()) + } + + results := s.gateway.ParseAndFilterMany(txs, filter) + if len(results) == 0 { + time.Sleep(tick) + continue + } + + tx := results[0].Transaction + txHash := liteapi.TransactionToHashString(results[0].Transaction) + + if !tx.IsSuccess() { + // should not happen + return errors.Errorf("transaction %q is not successful", txHash) + } + + // Note that this method has a check for noop + _, err = zetacore.AddOutboundTracker(ctx, chainID, nonce, txHash, nil, "", 0) + if err != nil { + return errors.Wrap(err, "unable to add outbound tracker") + } + + return nil + } + + return errors.Errorf("timeout exceeded (%s)", time.Since(start).String()) +} + +// creates a tx filter for this very withdrawal +func withdrawalFilter(w *toncontracts.Withdrawal) func(tx *toncontracts.Transaction) bool { + return func(tx *toncontracts.Transaction) bool { + if !tx.IsOutbound() || tx.Operation != toncontracts.OpWithdraw { + return false + } + + wd, err := tx.Withdrawal() + if err != nil { + return false + } + + return wd.Seqno == w.Seqno && wd.Sig == w.Sig + } +} diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index 34c94bf77d..e1d89df0fd 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -23,6 +23,8 @@ import ( solanasigner "github.com/zeta-chain/node/zetaclient/chains/solana/signer" "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" tonobserver "github.com/zeta-chain/node/zetaclient/chains/ton/observer" + tonsigner "github.com/zeta-chain/node/zetaclient/chains/ton/signer" + "github.com/zeta-chain/node/zetaclient/config" zctx "github.com/zeta-chain/node/zetaclient/context" "github.com/zeta-chain/node/zetaclient/db" "github.com/zeta-chain/node/zetaclient/keys" @@ -187,8 +189,25 @@ func syncSignerMap( addSigner(chainID, signer) case chain.IsTON(): - logger.Std.Error().Err(err).Msgf("TON signer is not implemented yet for chain id %d", chainID) - continue + cfg, found := app.Config().GetTONConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find TON config for chain %d", chainID) + continue + } + + tonClient, gateway, err := makeTONClient(ctx, cfg, chain.Params().GatewayAddress) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to create TON client for chain %d", chainID) + continue + } + + tonSigner := tonsigner.New( + base.NewSigner(*rawChain, tss, ts, logger), + tonClient, + gateway, + ) + + addSigner(chainID, tonSigner) default: logger.Std.Warn(). Int64("signer.chain_id", chain.ID()). @@ -435,21 +454,13 @@ func syncObserverMap( continue } - tonClient, err := liteapi.NewFromSource(ctx, cfg.LiteClientConfigURL) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to create TON liteapi for chain %d", chainID) - continue - } - - gatewayID, err := ton.ParseAccountID(params.GatewayAddress) + tonClient, gateway, err := makeTONClient(ctx, cfg, chain.Params().GatewayAddress) if err != nil { - logger.Std.Error().Err(err). - Msgf("Unable to parse gateway address %q for chain %d", params.GatewayAddress, chainID) + logger.Std.Error().Err(err).Msgf("Unable to create TON client for chain %d", chainID) continue } - gw := toncontracts.NewGateway(gatewayID) - tonObserver, err := tonobserver.New(baseObserver, tonClient, gw) + tonObserver, err := tonobserver.New(baseObserver, tonClient, gateway) if err != nil { logger.Std.Error().Err(err).Msgf("Unable to create TON observer for chain %d", chainID) continue @@ -469,3 +480,23 @@ func syncObserverMap( return added, removed, nil } + +func makeTONClient( + ctx context.Context, + cfg config.TONConfig, + gatewayAddr string, +) (*liteapi.Client, *toncontracts.Gateway, error) { + client, err := liteapi.NewFromSource(ctx, cfg.LiteClientConfigURL) + if err != nil { + return nil, nil, errors.Wrap(err, "Unable to create TON liteapi") + } + + gatewayID, err := ton.ParseAccountID(gatewayAddr) + if err != nil { + return nil, nil, errors.Wrapf(err, "Unable to parse gateway address %q", gatewayAddr) + } + + gw := toncontracts.NewGateway(gatewayID) + + return client, gw, nil +} diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index b63b7e3a00..b632b9234a 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -24,7 +24,10 @@ import ( btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" "github.com/zeta-chain/node/zetaclient/chains/interfaces" solanaobserver "github.com/zeta-chain/node/zetaclient/chains/solana/observer" + tonobserver "github.com/zeta-chain/node/zetaclient/chains/ton/observer" + tonsigner "github.com/zeta-chain/node/zetaclient/chains/ton/signer" zctx "github.com/zeta-chain/node/zetaclient/context" + "github.com/zeta-chain/node/zetaclient/logs" "github.com/zeta-chain/node/zetaclient/metrics" "github.com/zeta-chain/node/zetaclient/outboundprocessor" "github.com/zeta-chain/node/zetaclient/ratelimiter" @@ -92,10 +95,8 @@ func New( return nil, errors.New("signerMap or observerMap is nil") } - log := multiLogger{ - Logger: logger.Std.With().Str("module", "orchestrator").Logger(), - Sampled: logger.Std.With().Str("module", "orchestrator").Logger().Sample(defaultLogSampler), - } + logging := logger.Std.With().Str(logs.FieldModule, "orchestrator").Logger() + multiLog := multiLogger{Logger: logging, Sampled: logging.Sample(defaultLogSampler)} balance, err := client.GetZetaHotKeyBalance(ctx) if err != nil { @@ -116,7 +117,7 @@ func New( dbDirectory: dbDirectory, baseLogger: logger, - logger: log, + logger: multiLog, ts: ts, stop: make(chan struct{}), }, nil @@ -194,6 +195,16 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte Str("signer.gateway_address", params.GatewayAddress). Msgf("updated gateway address for chain %d", chainID) } + case chain.IsTON(): + newAddress := chain.Params().GatewayAddress + + if newAddress != signer.GetGatewayAddress() { + signer.SetGatewayAddress(newAddress) + oc.logger.Info(). + Str("signer.new_gateway_address", newAddress). + Int64("signer.chain_id", chainID). + Msg("set gateway address") + } } return signer, nil @@ -236,8 +247,9 @@ func (oc *Orchestrator) resolveObserver(app *zctx.AppContext, chainID int64) (in if !observertypes.ChainParamsEqual(curParams, *freshParams) { observer.SetChainParams(*freshParams) oc.logger.Info(). + Int64("observer.chain_id", chainID). Interface("observer.chain_params", *freshParams). - Msgf("updated chain params for chainID %d", chainID) + Msg("updated chain params") } return observer, nil @@ -379,14 +391,16 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { signer, err := oc.resolveSigner(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: unable to resolve signer for chain %d", chainID) + Int64(logs.FieldChain, chainID). + Msg("runScheduler: unable to resolve signer") continue } ob, err := oc.resolveObserver(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: resolveObserver failed for chain %d", chainID) + Int64(logs.FieldChain, chainID). + Msg("runScheduler: unable to resolve observer") continue } @@ -415,6 +429,8 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { oc.ScheduleCctxBTC(ctx, zetaHeight, chainID, cctxList, ob, signer) case chain.IsSolana(): oc.ScheduleCctxSolana(ctx, zetaHeight, chainID, cctxList, ob, signer) + case chain.IsTON(): + oc.ScheduleCCTXTON(ctx, zetaHeight, chainID, cctxList, ob, signer) default: oc.logger.Error().Msgf("runScheduler: no scheduler found chain %d", chainID) continue @@ -664,6 +680,87 @@ func (oc *Orchestrator) ScheduleCctxSolana( } } +// ScheduleCCTXTON schedules TON outbound keySign on each ZetaChain block +func (oc *Orchestrator) ScheduleCCTXTON( + ctx context.Context, + zetaHeight uint64, + chainID int64, + cctxList []*types.CrossChainTx, + observer interfaces.ChainObserver, + signer interfaces.ChainSigner, +) { + // should never happen + if _, ok := observer.(*tonobserver.Observer); !ok { + oc.logger.Error().Msg("ScheduleCCTXTON: observer is not TON") + return + } + + if _, ok := signer.(*tonsigner.Signer); !ok { + oc.logger.Error().Msg("ScheduleCCTXTON: signer is not TON") + return + } + + // Scheduler interval measured in zeta blocks. + // runScheduler() guarantees that this function is called every zeta block. + // Note that TON blockchain is async and doesn't have a concept of confirmations + // i.e. tx is finalized as soon as it's included in the next block (less than 6 seconds) + // #nosec G115 positive + interval := uint64(observer.ChainParams().OutboundScheduleInterval) + + shouldProcessOutbounds := zetaHeight%interval == 0 + + for i := range cctxList { + var ( + cctx = cctxList[i] + params = cctx.GetCurrentOutboundParam() + nonce = params.TssNonce + outboundID = outboundprocessor.ToOutboundID(cctx.Index, params.ReceiverChainId, nonce) + ) + + if params.ReceiverChainId != chainID { + // should not happen + oc.logger.Error().Msgf("ScheduleCCTXTON: outbound chain id mismatch (got %d)", params.ReceiverChainId) + continue + } + + // vote outbound if it's already confirmed + continueKeySign, err := observer.VoteOutboundIfConfirmed(ctx, cctx) + + switch { + case err != nil: + oc.logger.Error().Err(err).Uint64("outbound.nonce", nonce). + Msg("ScheduleCCTXTON: VoteOutboundIfConfirmed failed") + continue + case !continueKeySign: + oc.logger.Info().Uint64("outbound.nonce", nonce). + Msg("ScheduleCCTXTON: outbound already processed") + continue + case !shouldProcessOutbounds: + // well, let's wait for another block to (probably) trigger the processing + continue + } + + // try to sign and broadcast cctx to TON + task := func(ctx context.Context) error { + signer.TryProcessOutbound( + ctx, + cctx, + oc.outboundProc, + outboundID, + observer, + oc.zetacoreClient, + zetaHeight, + ) + + return nil + } + + // fire async task + taskLogger := oc.logger.Logger.With().Str("outbound.id", outboundID).Logger() + bg.Work(ctx, task, bg.WithName("TryProcessOutbound"), bg.WithLogger(taskLogger)) + } +} + // 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 { @@ -709,8 +806,8 @@ func (oc *Orchestrator) syncObserverSigner(ctx context.Context) error { if added+removed > 0 { oc.logger.Info(). - Int("signers.added", added). - Int("signers.removed", removed). + Int("signer.added", added). + Int("signer.removed", removed). Msg("synced signers") } diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index 2ab34b900e..d88006920f 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -31,12 +31,14 @@ func Test_GetUpdatedSigner(t *testing.T) { evmChain = chains.Ethereum btcChain = chains.BitcoinMainnet solChain = chains.SolanaMainnet + tonChain = chains.TONMainnet ) var ( evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) solChainParams = mocks.MockChainParams(solChain.ChainId, 100) + tonChainParams = mocks.MockChainParams(tonChain.ChainId, 100) ) solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID @@ -50,6 +52,9 @@ func Test_GetUpdatedSigner(t *testing.T) { solChainParamsNew := mocks.MockChainParams(solChain.ChainId, 100) solChainParamsNew.GatewayAddress = sample.SolanaAddress(t) + tonChainParamsNew := mocks.MockChainParams(tonChain.ChainId, 100) + tonChainParamsNew.GatewayAddress = sample.GenerateTONAccountID().ToRaw() + t.Run("signer should not be found", func(t *testing.T) { orchestrator := mockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) @@ -86,6 +91,23 @@ func Test_GetUpdatedSigner(t *testing.T) { require.NoError(t, err) require.Equal(t, solChainParamsNew.GatewayAddress, signer.GetGatewayAddress()) }) + + t.Run("should be able to update ton gateway address", func(t *testing.T) { + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, solChain, tonChain, + evmChainParams, btcChainParams, solChainParams, tonChainParams, + ) + + appContext := createAppContext(t, + evmChain, btcChain, solChain, tonChain, + evmChainParams, btcChainParams, solChainParamsNew, tonChainParamsNew, + ) + + // update signer with new gateway address + signer, err := orchestrator.resolveSigner(appContext, tonChain.ChainId) + require.NoError(t, err) + require.Equal(t, tonChainParamsNew.GatewayAddress, signer.GetGatewayAddress()) + }) } func Test_GetUpdatedChainObserver(t *testing.T) { @@ -94,15 +116,18 @@ func Test_GetUpdatedChainObserver(t *testing.T) { evmChain = chains.Ethereum btcChain = chains.BitcoinMainnet solChain = chains.SolanaMainnet + tonChain = chains.TONMainnet ) var ( evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) solChainParams = mocks.MockChainParams(solChain.ChainId, 100) + tonChainParams = mocks.MockChainParams(tonChain.ChainId, 100) ) solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID + tonChainParams.GatewayAddress = sample.GenerateTONAccountID().ToRaw() // new chain params in AppContext evmChainParamsNew := &observertypes.ChainParams{ @@ -153,6 +178,22 @@ func Test_GetUpdatedChainObserver(t *testing.T) { MinObserverDelegation: sdk.OneDec(), IsSupported: true, } + tonChainParamsNew := &observertypes.ChainParams{ + ChainId: tonChain.ChainId, + ConfirmationCount: 10, + GasPriceTicker: 5, + InboundTicker: 6, + OutboundTicker: 6, + WatchUtxoTicker: 1, + ZetaTokenContractAddress: "", + ConnectorContractAddress: "", + Erc20CustodyContractAddress: "", + OutboundScheduleInterval: 10, + OutboundScheduleLookahead: 10, + BallotThreshold: sdk.OneDec(), + MinObserverDelegation: sdk.OneDec(), + IsSupported: true, + } t.Run("evm chain observer should not be found", func(t *testing.T) { orchestrator := mockOrchestrator( @@ -284,6 +325,43 @@ func Test_GetUpdatedChainObserver(t *testing.T) { require.NotNil(t, chainOb) require.True(t, observertypes.ChainParamsEqual(*solChainParamsNew, chainOb.ChainParams())) }) + t.Run("ton chain observer should not be found", func(t *testing.T) { + orchestrator := mockOrchestrator( + t, + nil, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, + evmChainParams, + btcChainParams, + solChainParamsNew, + ) + + _, err := orchestrator.resolveObserver(appContext, tonChain.ChainId) + require.ErrorContains(t, err, "observer not found") + }) + t.Run("chain params in ton chain observer should be updated successfully", func(t *testing.T) { + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, tonChain, + evmChainParams, btcChainParams, tonChainParams, + ) + appContext := createAppContext(t, + evmChain, btcChain, tonChain, + evmChainParams, btcChainParams, tonChainParamsNew, + ) + + // update solana chain observer with new chain params + chainOb, err := orchestrator.resolveObserver(appContext, tonChain.ChainId) + require.NoError(t, err) + require.NotNil(t, chainOb) + require.True(t, observertypes.ChainParamsEqual(*tonChainParamsNew, chainOb.ChainParams())) + }) } func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { @@ -500,6 +578,9 @@ func mockOrchestrator(t *testing.T, zetaClient interfaces.ZetacoreClient, chains case chains.IsSolanaChain(cp.ChainId, nil): observers[cp.ChainId] = mocks.NewSolanaObserver(cp) signers[cp.ChainId] = mocks.NewSolanaSigner() + case chains.IsTONChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewTONObserver(cp) + signers[cp.ChainId] = mocks.NewTONSigner() default: t.Fatalf("mock orcestrator: unsupported chain %d", cp.ChainId) } @@ -526,6 +607,8 @@ func createAppContext(t *testing.T, chainsOrParams ...any) *zctx.AppContext { cfg.BTCChainConfigs[c.ChainId] = config.BTCConfig{RPCHost: "localhost"} case chains.IsSolanaChain(c.ChainId, nil): cfg.SolanaConfig = config.SolanaConfig{Endpoint: "localhost"} + case chains.IsTONChain(c.ChainId, nil): + cfg.TONConfig = config.TONConfig{LiteClientConfigURL: "localhost"} default: t.Fatalf("create app context: unsupported chain %d", c.ChainId) } diff --git a/zetaclient/testutils/mocks/chain_clients.go b/zetaclient/testutils/mocks/chain_clients.go index aa5e36889b..14ec720873 100644 --- a/zetaclient/testutils/mocks/chain_clients.go +++ b/zetaclient/testutils/mocks/chain_clients.go @@ -3,11 +3,22 @@ package mocks import ( "context" - crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + cc "github.com/zeta-chain/node/x/crosschain/types" observertypes "github.com/zeta-chain/node/x/observer/types" "github.com/zeta-chain/node/zetaclient/chains/interfaces" ) +type DummyObserver struct{} + +func (ob *DummyObserver) VoteOutboundIfConfirmed(_ context.Context, _ *cc.CrossChainTx) (bool, error) { + return false, nil +} + +func (ob *DummyObserver) Start(_ context.Context) {} +func (ob *DummyObserver) Stop() {} +func (ob *DummyObserver) SetChainParams(_ observertypes.ChainParams) {} +func (ob *DummyObserver) ChainParams() (_ observertypes.ChainParams) { return } + // ---------------------------------------------------------------------------- // EVMObserver // ---------------------------------------------------------------------------- @@ -15,23 +26,12 @@ var _ interfaces.ChainObserver = (*EVMObserver)(nil) // EVMObserver is a mock of evm chain observer for testing type EVMObserver struct { + DummyObserver chainParams observertypes.ChainParams } func NewEVMObserver(chainParams *observertypes.ChainParams) *EVMObserver { - return &EVMObserver{ - chainParams: *chainParams, - } -} - -func (ob *EVMObserver) Start(_ context.Context) {} -func (ob *EVMObserver) Stop() {} - -func (ob *EVMObserver) VoteOutboundIfConfirmed( - _ context.Context, - _ *crosschaintypes.CrossChainTx, -) (bool, error) { - return false, nil + return &EVMObserver{chainParams: *chainParams} } func (ob *EVMObserver) SetChainParams(chainParams observertypes.ChainParams) { @@ -42,14 +42,6 @@ func (ob *EVMObserver) ChainParams() observertypes.ChainParams { return ob.chainParams } -func (ob *EVMObserver) GetTxID(_ uint64) string { - return "" -} - -func (ob *EVMObserver) WatchInboundTracker(_ context.Context) error { - return nil -} - // ---------------------------------------------------------------------------- // BTCObserver // ---------------------------------------------------------------------------- @@ -57,24 +49,12 @@ var _ interfaces.ChainObserver = (*BTCObserver)(nil) // BTCObserver is a mock of btc chain observer for testing type BTCObserver struct { + DummyObserver chainParams observertypes.ChainParams } func NewBTCObserver(chainParams *observertypes.ChainParams) *BTCObserver { - return &BTCObserver{ - chainParams: *chainParams, - } -} - -func (ob *BTCObserver) Start(_ context.Context) {} - -func (ob *BTCObserver) Stop() {} - -func (ob *BTCObserver) VoteOutboundIfConfirmed( - _ context.Context, - _ *crosschaintypes.CrossChainTx, -) (bool, error) { - return false, nil + return &BTCObserver{chainParams: *chainParams} } func (ob *BTCObserver) SetChainParams(chainParams observertypes.ChainParams) { @@ -85,12 +65,6 @@ func (ob *BTCObserver) ChainParams() observertypes.ChainParams { return ob.chainParams } -func (ob *BTCObserver) GetTxID(_ uint64) string { - return "" -} - -func (ob *BTCObserver) WatchInboundTracker(_ context.Context) error { return nil } - // ---------------------------------------------------------------------------- // SolanaObserver // ---------------------------------------------------------------------------- @@ -98,24 +72,12 @@ var _ interfaces.ChainObserver = (*SolanaObserver)(nil) // SolanaObserver is a mock of solana chain observer for testing type SolanaObserver struct { + DummyObserver chainParams observertypes.ChainParams } func NewSolanaObserver(chainParams *observertypes.ChainParams) *SolanaObserver { - return &SolanaObserver{ - chainParams: *chainParams, - } -} - -func (ob *SolanaObserver) Start(_ context.Context) {} - -func (ob *SolanaObserver) Stop() {} - -func (ob *SolanaObserver) VoteOutboundIfConfirmed( - _ context.Context, - _ *crosschaintypes.CrossChainTx, -) (bool, error) { - return false, nil + return &SolanaObserver{chainParams: *chainParams} } func (ob *SolanaObserver) SetChainParams(chainParams observertypes.ChainParams) { @@ -126,8 +88,25 @@ func (ob *SolanaObserver) ChainParams() observertypes.ChainParams { return ob.chainParams } -func (ob *SolanaObserver) GetTxID(_ uint64) string { - return "" +// ---------------------------------------------------------------------------- +// TONObserver +// ---------------------------------------------------------------------------- +var _ interfaces.ChainObserver = (*TONObserver)(nil) + +// TONObserver is a mock of TON chain observer for testing +type TONObserver struct { + DummyObserver + chainParams observertypes.ChainParams +} + +func NewTONObserver(chainParams *observertypes.ChainParams) *TONObserver { + return &TONObserver{chainParams: *chainParams} } -func (ob *SolanaObserver) WatchInboundTracker(_ context.Context) error { return nil } +func (ob *TONObserver) SetChainParams(chainParams observertypes.ChainParams) { + ob.chainParams = chainParams +} + +func (ob *TONObserver) ChainParams() observertypes.ChainParams { + return ob.chainParams +} diff --git a/zetaclient/testutils/mocks/chain_signer.go b/zetaclient/testutils/mocks/chain_signer.go index cc0c65457f..790ee8fd92 100644 --- a/zetaclient/testutils/mocks/chain_signer.go +++ b/zetaclient/testutils/mocks/chain_signer.go @@ -11,6 +11,26 @@ import ( "github.com/zeta-chain/node/zetaclient/outboundprocessor" ) +type DummySigner struct{} + +func (s *DummySigner) TryProcessOutbound( + _ context.Context, + _ *crosschaintypes.CrossChainTx, + _ *outboundprocessor.Processor, + _ string, + _ interfaces.ChainObserver, + _ interfaces.ZetacoreClient, + _ uint64, +) { +} + +func (s *DummySigner) SetGatewayAddress(_ string) {} +func (s *DummySigner) GetGatewayAddress() (_ string) { return } +func (s *DummySigner) SetZetaConnectorAddress(_ ethcommon.Address) {} +func (s *DummySigner) SetERC20CustodyAddress(_ ethcommon.Address) {} +func (s *DummySigner) GetZetaConnectorAddress() (_ ethcommon.Address) { return } +func (s *DummySigner) GetERC20CustodyAddress() (_ ethcommon.Address) { return } + // ---------------------------------------------------------------------------- // EVMSigner // ---------------------------------------------------------------------------- @@ -18,6 +38,7 @@ var _ interfaces.ChainSigner = (*EVMSigner)(nil) // EVMSigner is a mock of evm chain signer for testing type EVMSigner struct { + DummySigner Chain chains.Chain ZetaConnectorAddress ethcommon.Address ERC20CustodyAddress ethcommon.Address @@ -35,24 +56,6 @@ func NewEVMSigner( } } -func (s *EVMSigner) TryProcessOutbound( - _ context.Context, - _ *crosschaintypes.CrossChainTx, - _ *outboundprocessor.Processor, - _ string, - _ interfaces.ChainObserver, - _ interfaces.ZetacoreClient, - _ uint64, -) { -} - -func (s *EVMSigner) SetGatewayAddress(_ string) { -} - -func (s *EVMSigner) GetGatewayAddress() string { - return "" -} - func (s *EVMSigner) SetZetaConnectorAddress(address ethcommon.Address) { s.ZetaConnectorAddress = address } @@ -75,45 +78,12 @@ func (s *EVMSigner) GetERC20CustodyAddress() ethcommon.Address { var _ interfaces.ChainSigner = (*BTCSigner)(nil) // BTCSigner is a mock of bitcoin chain signer for testing -type BTCSigner struct { -} +type BTCSigner = DummySigner func NewBTCSigner() *BTCSigner { return &BTCSigner{} } -func (s *BTCSigner) TryProcessOutbound( - _ context.Context, - _ *crosschaintypes.CrossChainTx, - _ *outboundprocessor.Processor, - _ string, - _ interfaces.ChainObserver, - _ interfaces.ZetacoreClient, - _ uint64, -) { -} - -func (s *BTCSigner) SetGatewayAddress(_ string) { -} - -func (s *BTCSigner) GetGatewayAddress() string { - return "" -} - -func (s *BTCSigner) SetZetaConnectorAddress(_ ethcommon.Address) { -} - -func (s *BTCSigner) SetERC20CustodyAddress(_ ethcommon.Address) { -} - -func (s *BTCSigner) GetZetaConnectorAddress() ethcommon.Address { - return ethcommon.Address{} -} - -func (s *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { - return ethcommon.Address{} -} - // ---------------------------------------------------------------------------- // SolanaSigner // ---------------------------------------------------------------------------- @@ -121,6 +91,7 @@ var _ interfaces.ChainSigner = (*SolanaSigner)(nil) // SolanaSigner is a mock of solana chain signer for testing type SolanaSigner struct { + DummySigner GatewayAddress string } @@ -128,17 +99,6 @@ func NewSolanaSigner() *SolanaSigner { return &SolanaSigner{} } -func (s *SolanaSigner) TryProcessOutbound( - _ context.Context, - _ *crosschaintypes.CrossChainTx, - _ *outboundprocessor.Processor, - _ string, - _ interfaces.ChainObserver, - _ interfaces.ZetacoreClient, - _ uint64, -) { -} - func (s *SolanaSigner) SetGatewayAddress(address string) { s.GatewayAddress = address } @@ -147,16 +107,25 @@ func (s *SolanaSigner) GetGatewayAddress() string { return s.GatewayAddress } -func (s *SolanaSigner) SetZetaConnectorAddress(_ ethcommon.Address) { +// ---------------------------------------------------------------------------- +// TONSigner +// ---------------------------------------------------------------------------- +var _ interfaces.ChainSigner = (*TONSigner)(nil) + +// TONSigner is a mock of TON chain signer for testing +type TONSigner struct { + DummySigner + GatewayAddress string } -func (s *SolanaSigner) SetERC20CustodyAddress(_ ethcommon.Address) { +func NewTONSigner() *TONSigner { + return &TONSigner{} } -func (s *SolanaSigner) GetZetaConnectorAddress() ethcommon.Address { - return ethcommon.Address{} +func (s *TONSigner) SetGatewayAddress(address string) { + s.GatewayAddress = address } -func (s *SolanaSigner) GetERC20CustodyAddress() ethcommon.Address { - return ethcommon.Address{} +func (s *TONSigner) GetGatewayAddress() string { + return s.GatewayAddress } diff --git a/zetaclient/testutils/mocks/ton_liteclient.go b/zetaclient/testutils/mocks/ton_liteclient.go index f11ccaf24c..0fa56f4ee6 100644 --- a/zetaclient/testutils/mocks/ton_liteclient.go +++ b/zetaclient/testutils/mocks/ton_liteclient.go @@ -5,6 +5,9 @@ package mocks import ( context "context" + liteapi "github.com/tonkeeper/tongo/liteapi" + liteclient "github.com/tonkeeper/tongo/liteclient" + mock "github.com/stretchr/testify/mock" tlb "github.com/tonkeeper/tongo/tlb" @@ -45,9 +48,37 @@ func (_m *LiteClient) GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt return r0, r1 } -// GetFirstTransaction provides a mock function with given fields: ctx, id -func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error) { - ret := _m.Called(ctx, id) +// GetConfigParams provides a mock function with given fields: ctx, mode, params +func (_m *LiteClient) GetConfigParams(ctx context.Context, mode liteapi.ConfigMode, params []uint32) (tlb.ConfigParams, error) { + ret := _m.Called(ctx, mode, params) + + if len(ret) == 0 { + panic("no return value specified for GetConfigParams") + } + + var r0 tlb.ConfigParams + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, liteapi.ConfigMode, []uint32) (tlb.ConfigParams, error)); ok { + return rf(ctx, mode, params) + } + if rf, ok := ret.Get(0).(func(context.Context, liteapi.ConfigMode, []uint32) tlb.ConfigParams); ok { + r0 = rf(ctx, mode, params) + } else { + r0 = ret.Get(0).(tlb.ConfigParams) + } + + if rf, ok := ret.Get(1).(func(context.Context, liteapi.ConfigMode, []uint32) error); ok { + r1 = rf(ctx, mode, params) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetFirstTransaction provides a mock function with given fields: ctx, acc +func (_m *LiteClient) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*ton.Transaction, int, error) { + ret := _m.Called(ctx, acc) if len(ret) == 0 { panic("no return value specified for GetFirstTransaction") @@ -57,10 +88,10 @@ func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID) var r1 int var r2 error if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) (*ton.Transaction, int, error)); ok { - return rf(ctx, id) + return rf(ctx, acc) } if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) *ton.Transaction); ok { - r0 = rf(ctx, id) + r0 = rf(ctx, acc) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*ton.Transaction) @@ -68,13 +99,13 @@ func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID) } if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID) int); ok { - r1 = rf(ctx, id) + r1 = rf(ctx, acc) } else { r1 = ret.Get(1).(int) } if rf, ok := ret.Get(2).(func(context.Context, ton.AccountID) error); ok { - r2 = rf(ctx, id) + r2 = rf(ctx, acc) } else { r2 = ret.Error(2) } @@ -82,9 +113,65 @@ func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID) return r0, r1, r2 } -// GetTransactionsSince provides a mock function with given fields: ctx, acc, lt, bits -func (_m *LiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error) { - ret := _m.Called(ctx, acc, lt, bits) +// GetMasterchainInfo provides a mock function with given fields: ctx +func (_m *LiteClient) GetMasterchainInfo(ctx context.Context) (liteclient.LiteServerMasterchainInfoC, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetMasterchainInfo") + } + + var r0 liteclient.LiteServerMasterchainInfoC + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (liteclient.LiteServerMasterchainInfoC, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) liteclient.LiteServerMasterchainInfoC); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(liteclient.LiteServerMasterchainInfoC) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransaction provides a mock function with given fields: ctx, acc, lt, hash +func (_m *LiteClient) GetTransaction(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) (ton.Transaction, error) { + ret := _m.Called(ctx, acc, lt, hash) + + if len(ret) == 0 { + panic("no return value specified for GetTransaction") + } + + var r0 ton.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) (ton.Transaction, error)); ok { + return rf(ctx, acc, lt, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) ton.Transaction); ok { + r0 = rf(ctx, acc, lt, hash) + } else { + r0 = ret.Get(0).(ton.Transaction) + } + + if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID, uint64, ton.Bits256) error); ok { + r1 = rf(ctx, acc, lt, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionsSince provides a mock function with given fields: ctx, acc, lt, hash +func (_m *LiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) ([]ton.Transaction, error) { + ret := _m.Called(ctx, acc, lt, hash) if len(ret) == 0 { panic("no return value specified for GetTransactionsSince") @@ -93,10 +180,10 @@ func (_m *LiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountI var r0 []ton.Transaction var r1 error if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) ([]ton.Transaction, error)); ok { - return rf(ctx, acc, lt, bits) + return rf(ctx, acc, lt, hash) } if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) []ton.Transaction); ok { - r0 = rf(ctx, acc, lt, bits) + r0 = rf(ctx, acc, lt, hash) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]ton.Transaction) @@ -104,7 +191,7 @@ func (_m *LiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountI } if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID, uint64, ton.Bits256) error); ok { - r1 = rf(ctx, acc, lt, bits) + r1 = rf(ctx, acc, lt, hash) } else { r1 = ret.Error(1) } diff --git a/zetaclient/testutils/mocks/ton_signerliteclient.go b/zetaclient/testutils/mocks/ton_signerliteclient.go new file mode 100644 index 0000000000..4cbd6360eb --- /dev/null +++ b/zetaclient/testutils/mocks/ton_signerliteclient.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + tlb "github.com/tonkeeper/tongo/tlb" + + ton "github.com/tonkeeper/tongo/ton" +) + +// SignerLiteClient is an autogenerated mock type for the LiteClient type +type SignerLiteClient struct { + mock.Mock +} + +// GetAccountState provides a mock function with given fields: ctx, accountID +func (_m *SignerLiteClient) GetAccountState(ctx context.Context, accountID ton.AccountID) (tlb.ShardAccount, error) { + ret := _m.Called(ctx, accountID) + + if len(ret) == 0 { + panic("no return value specified for GetAccountState") + } + + var r0 tlb.ShardAccount + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) (tlb.ShardAccount, error)); ok { + return rf(ctx, accountID) + } + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) tlb.ShardAccount); ok { + r0 = rf(ctx, accountID) + } else { + r0 = ret.Get(0).(tlb.ShardAccount) + } + + if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID) error); ok { + r1 = rf(ctx, accountID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionsSince provides a mock function with given fields: ctx, acc, lt, hash +func (_m *SignerLiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, hash ton.Bits256) ([]ton.Transaction, error) { + ret := _m.Called(ctx, acc, lt, hash) + + if len(ret) == 0 { + panic("no return value specified for GetTransactionsSince") + } + + var r0 []ton.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) ([]ton.Transaction, error)); ok { + return rf(ctx, acc, lt, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) []ton.Transaction); ok { + r0 = rf(ctx, acc, lt, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ton.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID, uint64, ton.Bits256) error); ok { + r1 = rf(ctx, acc, lt, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendMessage provides a mock function with given fields: ctx, payload +func (_m *SignerLiteClient) SendMessage(ctx context.Context, payload []byte) (uint32, error) { + ret := _m.Called(ctx, payload) + + if len(ret) == 0 { + panic("no return value specified for SendMessage") + } + + var r0 uint32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) (uint32, error)); ok { + return rf(ctx, payload) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte) uint32); ok { + r0 = rf(ctx, payload) + } else { + r0 = ret.Get(0).(uint32) + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, payload) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSignerLiteClient creates a new instance of SignerLiteClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSignerLiteClient(t interface { + mock.TestingT + Cleanup(func()) +}) *SignerLiteClient { + mock := &SignerLiteClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/zetaclient/testutils/mocks/tss_signer.go b/zetaclient/testutils/mocks/tss_signer.go index e0d9bd384c..78ce36649a 100644 --- a/zetaclient/testutils/mocks/tss_signer.go +++ b/zetaclient/testutils/mocks/tss_signer.go @@ -4,12 +4,14 @@ import ( "context" "crypto/ecdsa" "fmt" + "testing" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/interfaces" @@ -61,6 +63,29 @@ func NewTSSAthens3() *TSS { return NewMockTSS(chains.BscTestnet, testutils.TSSAddressEVMAthens3, testutils.TSSAddressBTCAthens3) } +func NewGeneratedTSS(t *testing.T, chain chains.Chain) *TSS { + pk, err := crypto.GenerateKey() + require.NoError(t, err) + + btcPub, err := btcec.ParsePubKey(crypto.FromECDSAPub(&pk.PublicKey)) + require.NoError(t, err) + + btcAddress, err := btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(btcPub.SerializeCompressed()), + &chaincfg.TestNet3Params, + ) + + require.NoError(t, err) + + return &TSS{ + paused: false, + chain: chain, + evmAddress: crypto.PubkeyToAddress(pk.PublicKey).Hex(), + btcAddress: btcAddress.String(), + PrivKey: pk, + } +} + // WithPrivKey sets the private key for the TSS func (s *TSS) WithPrivKey(privKey *ecdsa.PrivateKey) *TSS { s.PrivKey = privKey From 043e7768fe314c2ba46be3405980243afff843c2 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 30 Oct 2024 15:00:55 +0100 Subject: [PATCH 16/34] refactor(fungible): ZRC20 Lock/Unlock API do not depend on injected ABI (#3068) --- precompiles/bank/bank.go | 11 --- precompiles/bank/method_deposit.go | 18 +--- precompiles/bank/method_withdraw.go | 4 +- precompiles/staking/method_distribute.go | 2 +- precompiles/staking/staking.go | 11 --- .../keeper/zrc20_cosmos_coin_mapping_test.go | 59 +++++-------- .../keeper/zrc20_cosmos_coins_mapping.go | 29 ++---- x/fungible/keeper/zrc20_methods.go | 32 +++---- x/fungible/keeper/zrc20_methods_test.go | 88 +------------------ 9 files changed, 54 insertions(+), 200 deletions(-) diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index b92b4a8217..63311ca8e7 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" @@ -53,7 +52,6 @@ type Contract struct { bankKeeper bank.Keeper fungibleKeeper fungiblekeeper.Keeper - zrc20ABI *abi.ABI cdc codec.Codec kvGasConfig storetypes.GasConfig } @@ -70,19 +68,10 @@ func NewIBankContract( fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) } - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - if err != nil { - ctx.Logger().Error("bank contract failed to get ZRC20 ABI", "error", err) - return nil - } - return &Contract{ BaseContract: precompiletypes.NewBaseContract(ContractAddress), bankKeeper: bankKeeper, fungibleKeeper: fungibleKeeper, - zrc20ABI: zrc20ABI, cdc: cdc, kvGasConfig: kvGasConfig, } diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 76c21d926a..43ba2a492f 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -61,14 +61,7 @@ func (c *Contract) deposit( // Check for enough balance. // function balanceOf(address account) public view virtual override returns (uint256) - resBalanceOf, err := c.CallContract( - ctx, - &c.fungibleKeeper, - c.zrc20ABI, - zrc20Addr, - "balanceOf", - []interface{}{caller}, - ) + balance, err := c.fungibleKeeper.ZRC20BalanceOf(ctx, zrc20Addr, caller) if err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "balanceOf", @@ -76,13 +69,6 @@ func (c *Contract) deposit( } } - balance, ok := resBalanceOf[0].(*big.Int) - if !ok { - return nil, &precompiletypes.ErrUnexpected{ - Got: "ZRC20 balanceOf returned an unexpected type", - } - } - if balance.Cmp(amount) < 0 || balance.Cmp(big.NewInt(0)) <= 0 { return nil, &precompiletypes.ErrInvalidAmount{ Got: balance.String(), @@ -100,7 +86,7 @@ func (c *Contract) deposit( } // 2. Effect: subtract balance. - if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { + if err := c.fungibleKeeper.LockZRC20(ctx, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index a1308146c1..24f83bf5e4 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -85,7 +85,7 @@ func (c *Contract) withdraw( } // Check if bank address has enough ZRC20 balance. - if err := c.fungibleKeeper.CheckZRC20Balance(ctx, c.zrc20ABI, zrc20Addr, c.Address(), amount); err != nil { + if err := c.fungibleKeeper.CheckZRC20Balance(ctx, zrc20Addr, c.Address(), amount); err != nil { return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: err.Error(), @@ -108,7 +108,7 @@ func (c *Contract) withdraw( } // 3. Interactions: send ZRC20. - if err := c.fungibleKeeper.UnlockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, c.Address(), amount); err != nil { + if err := c.fungibleKeeper.UnlockZRC20(ctx, zrc20Addr, caller, c.Address(), amount); err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "UnlockZRC20InBank", Got: err.Error(), diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index 1fa7351505..c173517aeb 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -52,7 +52,7 @@ func (c *Contract) distribute( // - spender is the staking contract address (c.Address()). // - owner is the caller address. // - locker is the bank address. Assets are locked under this address to prevent liquidity fragmentation. - if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, bank.ContractAddress, amount); err != nil { + if err := c.fungibleKeeper.LockZRC20(ctx, zrc20Addr, c.Address(), caller, bank.ContractAddress, amount); err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 3408a2b09e..4d8115336a 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" @@ -66,7 +65,6 @@ type Contract struct { stakingKeeper stakingkeeper.Keeper fungibleKeeper fungiblekeeper.Keeper bankKeeper bankkeeper.Keeper - zrc20ABI *abi.ABI cdc codec.Codec kvGasConfig storetypes.GasConfig } @@ -84,20 +82,11 @@ func NewIStakingContract( fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) } - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - if err != nil { - ctx.Logger().Error("staking contract failed to get ZRC20 ABI", "error", err) - return nil - } - return &Contract{ BaseContract: precompiletypes.NewBaseContract(ContractAddress), stakingKeeper: *stakingKeeper, fungibleKeeper: fungibleKeeper, bankKeeper: bankKeeper, - zrc20ABI: zrc20ABI, cdc: cdc, kvGasConfig: kvGasConfig, } diff --git a/x/fungible/keeper/zrc20_cosmos_coin_mapping_test.go b/x/fungible/keeper/zrc20_cosmos_coin_mapping_test.go index 16803a917c..c19866e217 100644 --- a/x/fungible/keeper/zrc20_cosmos_coin_mapping_test.go +++ b/x/fungible/keeper/zrc20_cosmos_coin_mapping_test.go @@ -38,28 +38,21 @@ func Test_LockZRC20(t *testing.T) { t.Run("should fail when trying to lock zero amount", func(t *testing.T) { // Check lock with zero amount. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, locker, owner, locker, big.NewInt(0)) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, ts.zrc20Address, locker, owner, locker, big.NewInt(0)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrInvalidAmount) }) - t.Run("should fail when ZRC20 ABI is not properly initialized", func(t *testing.T) { - // Check lock with nil ABI. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, nil, ts.zrc20Address, locker, owner, locker, big.NewInt(10)) - require.Error(t, err) - require.ErrorIs(t, err, fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when trying to lock a zero address ZRC20", func(t *testing.T) { // Check lock with ZRC20 zero address. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, common.Address{}, locker, owner, locker, big.NewInt(10)) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, common.Address{}, locker, owner, locker, big.NewInt(10)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrZRC20ZeroAddress) }) t.Run("should fail when trying to lock a non whitelisted ZRC20", func(t *testing.T) { // Check lock with non whitelisted ZRC20. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, sample.EthAddress(), locker, owner, locker, big.NewInt(10)) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, sample.EthAddress(), locker, owner, locker, big.NewInt(10)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrZRC20NotWhiteListed) }) @@ -70,7 +63,6 @@ func Test_LockZRC20(t *testing.T) { // Check lock with higher amount than totalSupply. err = ts.fungibleKeeper.LockZRC20( ts.ctx, - zrc20ABI, ts.zrc20Address, locker, owner, @@ -85,7 +77,7 @@ func Test_LockZRC20(t *testing.T) { approveAllowance(t, ts, zrc20ABI, owner, locker, big.NewInt(1001)) // Check allowance smaller, equal and bigger than the amount. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, locker, owner, locker, big.NewInt(1001)) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, ts.zrc20Address, locker, owner, locker, big.NewInt(1001)) require.Error(t, err) // We do not check in LockZRC20 explicitly if the amount is bigger than the balance. @@ -97,7 +89,7 @@ func Test_LockZRC20(t *testing.T) { approveAllowance(t, ts, zrc20ABI, owner, locker, allowanceTotal) // Check allowance smaller, equal and bigger than the amount. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, locker, owner, locker, higherThanAllowance) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, ts.zrc20Address, locker, owner, locker, higherThanAllowance) require.Error(t, err) require.Contains(t, err.Error(), "invalid allowance, got 100") }) @@ -105,14 +97,14 @@ func Test_LockZRC20(t *testing.T) { t.Run("should pass when trying to lock a valid approved amount", func(t *testing.T) { approveAllowance(t, ts, zrc20ABI, owner, locker, allowanceTotal) - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, locker, owner, locker, allowanceTotal) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, ts.zrc20Address, locker, owner, locker, allowanceTotal) require.NoError(t, err) - ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, owner) + ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, owner) require.NoError(t, err) require.Equal(t, uint64(900), ownerBalance.Uint64()) - lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, locker) + lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, locker) require.NoError(t, err) require.Equal(t, uint64(100), lockerBalance.Uint64()) }) @@ -122,7 +114,6 @@ func Test_LockZRC20(t *testing.T) { err = ts.fungibleKeeper.LockZRC20( ts.ctx, - zrc20ABI, ts.zrc20Address, locker, owner, @@ -132,11 +123,11 @@ func Test_LockZRC20(t *testing.T) { require.NoError(t, err) // Note that balances are cumulative for all tests. That's why we check 801 and 199 here. - ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, owner) + ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, owner) require.NoError(t, err) require.Equal(t, uint64(801), ownerBalance.Uint64()) - lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, locker) + lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, locker) require.NoError(t, err) require.Equal(t, uint64(199), lockerBalance.Uint64()) }) @@ -164,48 +155,42 @@ func Test_UnlockZRC20(t *testing.T) { approveAllowance(t, ts, zrc20ABI, owner, locker, allowanceTotal) // Lock 100 ZRC20. - err = ts.fungibleKeeper.LockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, locker, owner, locker, allowanceTotal) + err = ts.fungibleKeeper.LockZRC20(ts.ctx, ts.zrc20Address, locker, owner, locker, allowanceTotal) require.NoError(t, err) t.Run("should fail when trying to unlock zero amount", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, owner, locker, big.NewInt(0)) + err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, ts.zrc20Address, owner, locker, big.NewInt(0)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrInvalidAmount) }) - t.Run("should fail when ZRC20 ABI is not properly initialized", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, nil, ts.zrc20Address, owner, locker, big.NewInt(10)) - require.Error(t, err) - require.ErrorIs(t, err, fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when trying to unlock a zero address ZRC20", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, zrc20ABI, common.Address{}, owner, locker, big.NewInt(10)) + err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, common.Address{}, owner, locker, big.NewInt(10)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrZRC20ZeroAddress) }) t.Run("should fail when trying to unlock a non whitelisted ZRC20", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, zrc20ABI, sample.EthAddress(), owner, locker, big.NewInt(10)) + err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, sample.EthAddress(), owner, locker, big.NewInt(10)) require.Error(t, err) require.ErrorIs(t, err, fungibletypes.ErrZRC20NotWhiteListed) }) t.Run("should fail when trying to unlock an amount bigger than locker's balance", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, owner, locker, big.NewInt(1001)) + err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, ts.zrc20Address, owner, locker, big.NewInt(1001)) require.Error(t, err) require.Contains(t, err.Error(), "invalid balance, got 100") }) t.Run("should pass when trying to unlock a correct amount", func(t *testing.T) { - err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, zrc20ABI, ts.zrc20Address, owner, locker, allowanceTotal) + err = ts.fungibleKeeper.UnlockZRC20(ts.ctx, ts.zrc20Address, owner, locker, allowanceTotal) require.NoError(t, err) - ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, owner) + ownerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, owner) require.NoError(t, err) require.Equal(t, uint64(1000), ownerBalance.Uint64()) - lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, locker) + lockerBalance, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, locker) require.NoError(t, err) require.Equal(t, uint64(0), lockerBalance.Uint64()) }) @@ -232,13 +217,13 @@ func Test_CheckZRC20Allowance(t *testing.T) { ts.fungibleKeeper.DepositZRC20(ts.ctx, ts.zrc20Address, fungibletypes.ModuleAddressEVM, depositTotal) t.Run("should fail when checking zero amount", func(t *testing.T) { - err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, zrc20ABI, owner, spender, ts.zrc20Address, big.NewInt(0)) + err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, owner, spender, ts.zrc20Address, big.NewInt(0)) require.Error(t, err) require.ErrorAs(t, err, &fungibletypes.ErrInvalidAmount) }) t.Run("should fail when allowance is not approved", func(t *testing.T) { - err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, zrc20ABI, owner, spender, ts.zrc20Address, big.NewInt(10)) + err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, owner, spender, ts.zrc20Address, big.NewInt(10)) require.Error(t, err) require.Contains(t, err.Error(), "invalid allowance, got 0") }) @@ -248,7 +233,6 @@ func Test_CheckZRC20Allowance(t *testing.T) { err = ts.fungibleKeeper.CheckZRC20Allowance( ts.ctx, - zrc20ABI, owner, spender, ts.zrc20Address, @@ -261,7 +245,7 @@ func Test_CheckZRC20Allowance(t *testing.T) { t.Run("should pass when checking the same amount as approved", func(t *testing.T) { approveAllowance(t, ts, zrc20ABI, owner, spender, allowanceTotal) - err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, zrc20ABI, owner, spender, ts.zrc20Address, allowanceTotal) + err = ts.fungibleKeeper.CheckZRC20Allowance(ts.ctx, owner, spender, ts.zrc20Address, allowanceTotal) require.NoError(t, err) }) @@ -270,7 +254,6 @@ func Test_CheckZRC20Allowance(t *testing.T) { err = ts.fungibleKeeper.CheckZRC20Allowance( ts.ctx, - zrc20ABI, owner, spender, ts.zrc20Address, diff --git a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go index 7ac3d7ef22..ddf86c7531 100644 --- a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go +++ b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go @@ -6,7 +6,6 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/node/pkg/crypto" @@ -19,27 +18,26 @@ import ( // it has to be implemented by the caller of this function. func (k Keeper) LockZRC20( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, spender, owner, locker common.Address, amount *big.Int, ) error { // owner is the EOA owner of the ZRC20 tokens. // spender is the EOA allowed to spend ZRC20 on owner's behalf. // locker is the address that will lock the ZRC20 tokens, i.e: bank precompile. - if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, spender, zrc20Address, amount); err != nil { + if err := k.CheckZRC20Allowance(ctx, owner, spender, zrc20Address, amount); err != nil { return errors.Wrap(err, "failed allowance check") } // Check amount_to_be_locked <= total_erc20_balance - already_locked // Max amount of ZRC20 tokens that exists in zEVM are the total supply. - totalSupply, err := k.ZRC20TotalSupply(ctx, zrc20ABI, zrc20Address) + totalSupply, err := k.ZRC20TotalSupply(ctx, zrc20Address) if err != nil { return errors.Wrap(err, "failed totalSupply check") } // The alreadyLocked amount is the amount of ZRC20 tokens that have been locked by the locker. // TODO: Implement list of whitelisted locker addresses (https://github.com/zeta-chain/node/issues/2991) - alreadyLocked, err := k.ZRC20BalanceOf(ctx, zrc20ABI, zrc20Address, locker) + alreadyLocked, err := k.ZRC20BalanceOf(ctx, zrc20Address, locker) if err != nil { return errors.Wrap(err, "failed getting the ZRC20 already locked amount") } @@ -50,7 +48,7 @@ func (k Keeper) LockZRC20( // Initiate a transferFrom the owner to the locker. This will lock the ZRC20 tokens. // locker has to initiate the transaction and have enough allowance from owner. - transferred, err := k.ZRC20TransferFrom(ctx, zrc20ABI, zrc20Address, spender, owner, locker, amount) + transferred, err := k.ZRC20TransferFrom(ctx, zrc20Address, spender, owner, locker, amount) if err != nil { return errors.Wrap(err, "failed executing transferFrom") } @@ -67,17 +65,16 @@ func (k Keeper) LockZRC20( // the owner has enough collateral (cosmos coins) to be exchanged (burnt) for the ZRC20 tokens. func (k Keeper) UnlockZRC20( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, owner, locker common.Address, amount *big.Int, ) error { // Check if the account locking the ZRC20 tokens has enough balance. - if err := k.CheckZRC20Balance(ctx, zrc20ABI, zrc20Address, locker, amount); err != nil { + if err := k.CheckZRC20Balance(ctx, zrc20Address, locker, amount); err != nil { return errors.Wrap(err, "failed balance check") } // transfer from the EOA locking the assets to the owner. - transferred, err := k.ZRC20Transfer(ctx, zrc20ABI, zrc20Address, locker, owner, amount) + transferred, err := k.ZRC20Transfer(ctx, zrc20Address, locker, owner, amount) if err != nil { return errors.Wrap(err, "failed executing transfer") } @@ -93,14 +90,9 @@ func (k Keeper) UnlockZRC20( // is equal or greater than the provided amount. func (k Keeper) CheckZRC20Allowance( ctx sdk.Context, - zrc20ABI *abi.ABI, owner, spender, zrc20Address common.Address, amount *big.Int, ) error { - if zrc20ABI == nil { - return fungibletypes.ErrZRC20NilABI - } - if amount.Sign() <= 0 || amount == nil { return fungibletypes.ErrInvalidAmount } @@ -113,7 +105,7 @@ func (k Keeper) CheckZRC20Allowance( return errors.Wrap(err, "ZRC20 is not valid") } - allowanceValue, err := k.ZRC20Allowance(ctx, zrc20ABI, zrc20Address, owner, spender) + allowanceValue, err := k.ZRC20Allowance(ctx, zrc20Address, owner, spender) if err != nil { return errors.Wrap(err, "failed while checking spender's allowance") } @@ -129,14 +121,9 @@ func (k Keeper) CheckZRC20Allowance( // is equal or greater than the provided amount. func (k Keeper) CheckZRC20Balance( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, owner common.Address, amount *big.Int, ) error { - if zrc20ABI == nil { - return fungibletypes.ErrZRC20NilABI - } - if amount.Sign() <= 0 || amount == nil { return fungibletypes.ErrInvalidAmount } @@ -151,7 +138,7 @@ func (k Keeper) CheckZRC20Balance( // Check the ZRC20 balance of a given account. // function balanceOf(address account) - balance, err := k.ZRC20BalanceOf(ctx, zrc20ABI, zrc20Address, owner) + balance, err := k.ZRC20BalanceOf(ctx, zrc20Address, owner) if err != nil { return errors.Wrap(err, "failed getting owner's ZRC20 balance") } diff --git a/x/fungible/keeper/zrc20_methods.go b/x/fungible/keeper/zrc20_methods.go index 5c9f2d4645..4d7c7f5174 100644 --- a/x/fungible/keeper/zrc20_methods.go +++ b/x/fungible/keeper/zrc20_methods.go @@ -6,8 +6,8 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/node/pkg/crypto" fungibletypes "github.com/zeta-chain/node/x/fungible/types" @@ -25,11 +25,11 @@ const ( // The allowance has to be previously approved by the ZRC20 tokens owner. func (k Keeper) ZRC20Allowance( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, owner, spender common.Address, ) (*big.Int, error) { - if zrc20ABI == nil { - return nil, fungibletypes.ErrZRC20NilABI + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return nil, err } if crypto.IsEmptyAddress(owner) || crypto.IsEmptyAddress(spender) { @@ -82,11 +82,11 @@ func (k Keeper) ZRC20Allowance( // ZRC20BalanceOf checks the ZRC20 balance of a given EOA. func (k Keeper) ZRC20BalanceOf( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, owner common.Address, ) (*big.Int, error) { - if zrc20ABI == nil { - return nil, fungibletypes.ErrZRC20NilABI + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return nil, err } if crypto.IsEmptyAddress(owner) { @@ -138,11 +138,11 @@ func (k Keeper) ZRC20BalanceOf( // ZRC20TotalSupply returns the total supply of a ZRC20 token. func (k Keeper) ZRC20TotalSupply( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address common.Address, ) (*big.Int, error) { - if zrc20ABI == nil { - return nil, fungibletypes.ErrZRC20NilABI + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return nil, err } if err := k.IsValidZRC20(ctx, zrc20Address); err != nil { @@ -189,12 +189,12 @@ func (k Keeper) ZRC20TotalSupply( // ZRC20Transfer transfers ZRC20 tokens from the sender to the recipient. func (k Keeper) ZRC20Transfer( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, from, to common.Address, amount *big.Int, ) (bool, error) { - if zrc20ABI == nil { - return false, fungibletypes.ErrZRC20NilABI + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return false, err } if crypto.IsEmptyAddress(from) || crypto.IsEmptyAddress(to) { @@ -249,12 +249,12 @@ func (k Keeper) ZRC20Transfer( // Requisite: the original EOA must have approved the spender to spend the tokens. func (k Keeper) ZRC20TransferFrom( ctx sdk.Context, - zrc20ABI *abi.ABI, zrc20Address, spender, from, to common.Address, amount *big.Int, ) (bool, error) { - if zrc20ABI == nil { - return false, fungibletypes.ErrZRC20NilABI + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return false, err } if crypto.IsEmptyAddress(from) || crypto.IsEmptyAddress(to) || crypto.IsEmptyAddress(spender) { diff --git a/x/fungible/keeper/zrc20_methods_test.go b/x/fungible/keeper/zrc20_methods_test.go index 7b124f3050..3d64a05928 100644 --- a/x/fungible/keeper/zrc20_methods_test.go +++ b/x/fungible/keeper/zrc20_methods_test.go @@ -14,23 +14,11 @@ import ( ) func Test_ZRC20Allowance(t *testing.T) { - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - require.NoError(t, err) - ts := setupChain(t) - t.Run("should fail when ZRC20ABI is nil", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20Allowance(ts.ctx, nil, ts.zrc20Address, common.Address{}, common.Address{}) - require.Error(t, err) - require.ErrorAs(t, err, &fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when owner is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Allowance( ts.ctx, - zrc20ABI, ts.zrc20Address, common.Address{}, sample.EthAddress(), @@ -42,7 +30,6 @@ func Test_ZRC20Allowance(t *testing.T) { t.Run("should fail when spender is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Allowance( ts.ctx, - zrc20ABI, ts.zrc20Address, sample.EthAddress(), common.Address{}, @@ -54,7 +41,6 @@ func Test_ZRC20Allowance(t *testing.T) { t.Run("should fail when zrc20 address is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Allowance( ts.ctx, - zrc20ABI, common.Address{}, sample.EthAddress(), fungibletypes.ModuleAddressEVM, @@ -66,7 +52,6 @@ func Test_ZRC20Allowance(t *testing.T) { t.Run("should pass with correct input", func(t *testing.T) { allowance, err := ts.fungibleKeeper.ZRC20Allowance( ts.ctx, - zrc20ABI, ts.zrc20Address, fungibletypes.ModuleAddressEVM, sample.EthAddress(), @@ -77,27 +62,16 @@ func Test_ZRC20Allowance(t *testing.T) { } func Test_ZRC20BalanceOf(t *testing.T) { - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - require.NoError(t, err) - ts := setupChain(t) - t.Run("should fail when ZRC20ABI is nil", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, nil, ts.zrc20Address, common.Address{}) - require.Error(t, err) - require.ErrorAs(t, err, &fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when owner is zero address", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, ts.zrc20Address, common.Address{}) + _, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, ts.zrc20Address, common.Address{}) require.Error(t, err) require.ErrorAs(t, err, &fungibletypes.ErrZeroAddress) }) t.Run("should fail when zrc20 address is zero address", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, zrc20ABI, common.Address{}, sample.EthAddress()) + _, err := ts.fungibleKeeper.ZRC20BalanceOf(ts.ctx, common.Address{}, sample.EthAddress()) require.Error(t, err) require.ErrorAs(t, err, &fungibletypes.ErrZRC20ZeroAddress) }) @@ -105,7 +79,6 @@ func Test_ZRC20BalanceOf(t *testing.T) { t.Run("should pass with correct input", func(t *testing.T) { balance, err := ts.fungibleKeeper.ZRC20BalanceOf( ts.ctx, - zrc20ABI, ts.zrc20Address, fungibletypes.ModuleAddressEVM, ) @@ -115,61 +88,31 @@ func Test_ZRC20BalanceOf(t *testing.T) { } func Test_ZRC20TotalSupply(t *testing.T) { - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - require.NoError(t, err) - ts := setupChain(t) - t.Run("should fail when ZRC20ABI is nil", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20TotalSupply(ts.ctx, nil, ts.zrc20Address) - require.Error(t, err) - require.ErrorAs(t, err, &fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when zrc20 address is zero address", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20TotalSupply(ts.ctx, zrc20ABI, common.Address{}) + _, err := ts.fungibleKeeper.ZRC20TotalSupply(ts.ctx, common.Address{}) require.Error(t, err) require.ErrorAs(t, err, &fungibletypes.ErrZRC20ZeroAddress) }) t.Run("should pass with correct input", func(t *testing.T) { - totalSupply, err := ts.fungibleKeeper.ZRC20TotalSupply(ts.ctx, zrc20ABI, ts.zrc20Address) + totalSupply, err := ts.fungibleKeeper.ZRC20TotalSupply(ts.ctx, ts.zrc20Address) require.NoError(t, err) require.Equal(t, uint64(10000000), totalSupply.Uint64()) }) } func Test_ZRC20Transfer(t *testing.T) { - // Instantiate the ZRC20 ABI only one time. - // This avoids instantiating it every time deposit or withdraw are called. - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - require.NoError(t, err) - ts := setupChain(t) // Make sure sample.EthAddress() exists as an ethermint account in state. accAddress := sdk.AccAddress(sample.EthAddress().Bytes()) ts.fungibleKeeper.GetAuthKeeper().SetAccount(ts.ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) - t.Run("should fail when ZRC20ABI is nil", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20Transfer( - ts.ctx, - nil, - ts.zrc20Address, - common.Address{}, - common.Address{}, - big.NewInt(0), - ) - require.Error(t, err) - require.ErrorAs(t, err, &fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when owner is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Transfer( ts.ctx, - zrc20ABI, ts.zrc20Address, common.Address{}, sample.EthAddress(), @@ -182,7 +125,6 @@ func Test_ZRC20Transfer(t *testing.T) { t.Run("should fail when spender is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Transfer( ts.ctx, - zrc20ABI, ts.zrc20Address, sample.EthAddress(), common.Address{}, @@ -195,7 +137,6 @@ func Test_ZRC20Transfer(t *testing.T) { t.Run("should fail when zrc20 address is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20Transfer( ts.ctx, - zrc20ABI, common.Address{}, sample.EthAddress(), fungibletypes.ModuleAddressEVM, @@ -209,7 +150,6 @@ func Test_ZRC20Transfer(t *testing.T) { ts.fungibleKeeper.DepositZRC20(ts.ctx, ts.zrc20Address, fungibletypes.ModuleAddressEVM, big.NewInt(10)) transferred, err := ts.fungibleKeeper.ZRC20Transfer( ts.ctx, - zrc20ABI, ts.zrc20Address, fungibletypes.ModuleAddressEVM, sample.EthAddress(), @@ -232,24 +172,9 @@ func Test_ZRC20TransferFrom(t *testing.T) { accAddress := sdk.AccAddress(sample.EthAddress().Bytes()) ts.fungibleKeeper.GetAuthKeeper().SetAccount(ts.ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) - t.Run("should fail when ZRC20ABI is nil", func(t *testing.T) { - _, err := ts.fungibleKeeper.ZRC20TransferFrom( - ts.ctx, - nil, - ts.zrc20Address, - common.Address{}, - common.Address{}, - common.Address{}, - big.NewInt(0), - ) - require.Error(t, err) - require.ErrorAs(t, err, &fungibletypes.ErrZRC20NilABI) - }) - t.Run("should fail when from is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, ts.zrc20Address, sample.EthAddress(), common.Address{}, @@ -263,7 +188,6 @@ func Test_ZRC20TransferFrom(t *testing.T) { t.Run("should fail when to is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, ts.zrc20Address, sample.EthAddress(), sample.EthAddress(), @@ -277,7 +201,6 @@ func Test_ZRC20TransferFrom(t *testing.T) { t.Run("should fail when spender is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, ts.zrc20Address, common.Address{}, sample.EthAddress(), @@ -291,7 +214,6 @@ func Test_ZRC20TransferFrom(t *testing.T) { t.Run("should fail when zrc20 address is zero address", func(t *testing.T) { _, err := ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, common.Address{}, sample.EthAddress(), sample.EthAddress(), @@ -309,7 +231,6 @@ func Test_ZRC20TransferFrom(t *testing.T) { // Transferring the tokens with transferFrom without approval should fail. _, err = ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, ts.zrc20Address, fungibletypes.ModuleAddressEVM, sample.EthAddress(), @@ -329,7 +250,6 @@ func Test_ZRC20TransferFrom(t *testing.T) { // Transferring the tokens with transferFrom without approval should fail. _, err = ts.fungibleKeeper.ZRC20TransferFrom( ts.ctx, - zrc20ABI, ts.zrc20Address, fungibletypes.ModuleAddressEVM, sample.EthAddress(), From 6ef5c57d6d3ca6a0ce813d35cf86a9483fffde23 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Wed, 30 Oct 2024 15:50:07 +0100 Subject: [PATCH 17/34] test(`e2e`): improve E2E reliability for tests on live network (#3061) * test(e2e): reduce gas limit used for withdraw and call * try 200k * 250k * use random string for payloads * fix lint * renaming * allow test with no args * fix lint --- cmd/zetae2e/run.go | 20 ++++++++++--------- e2e/e2etests/helpers.go | 11 ++++++++++ .../test_v2_erc20_deposit_and_call.go | 10 +++++----- ...erc20_deposit_and_call_revert_with_call.go | 12 +++++------ ...st_v2_erc20_withdraw_and_arbitrary_call.go | 10 +++++----- .../test_v2_erc20_withdraw_and_call.go | 12 +++++------ ...rc20_withdraw_and_call_revert_with_call.go | 12 +++++------ e2e/e2etests/test_v2_eth_deposit_and_call.go | 10 +++++----- ...2_eth_deposit_and_call_revert_with_call.go | 12 +++++------ ...test_v2_eth_withdraw_and_arbitrary_call.go | 10 +++++----- e2e/e2etests/test_v2_eth_withdraw_and_call.go | 12 +++++------ ..._eth_withdraw_and_call_revert_with_call.go | 12 +++++------ ..._eth_withdraw_and_call_through_contract.go | 10 +++++----- e2e/e2etests/test_v2_evm_to_zevm_call.go | 10 +++++----- .../test_v2_zevm_to_evm_arbitrary_call.go | 10 +++++----- e2e/e2etests/test_v2_zevm_to_evm_call.go | 12 +++++------ ...st_v2_zevm_to_evm_call_through_contract.go | 12 +++++------ e2e/runner/v2_zevm.go | 4 ++-- 18 files changed, 107 insertions(+), 94 deletions(-) diff --git a/cmd/zetae2e/run.go b/cmd/zetae2e/run.go index f58e5b51cf..fe52f056d5 100644 --- a/cmd/zetae2e/run.go +++ b/cmd/zetae2e/run.go @@ -155,20 +155,22 @@ func runE2ETest(cmd *cobra.Command, args []string) error { // parseCmdArgsToE2ETestRunConfig parses command-line arguments into a slice of E2ETestRunConfig structs. func parseCmdArgsToE2ETestRunConfig(args []string) ([]runner.E2ETestRunConfig, error) { - tests := []runner.E2ETestRunConfig{} + tests := make([]runner.E2ETestRunConfig, 0, len(args)) + for _, arg := range args { parts := strings.SplitN(arg, ":", 2) - if len(parts) != 2 { - return nil, errors.New("command arguments should be in format: testName:testArgs") - } - if parts[0] == "" { + testName := parts[0] + if testName == "" { return nil, errors.New("missing testName") } - testName := parts[0] - testArgs := []string{} - if parts[1] != "" { - testArgs = strings.Split(parts[1], ",") + + var testArgs []string + if len(parts) > 1 { + if parts[1] != "" { + testArgs = strings.Split(parts[1], ",") + } } + tests = append(tests, runner.E2ETestRunConfig{ Name: testName, Args: testArgs, diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go index 64f0920c2a..a7a997d70b 100644 --- a/e2e/e2etests/helpers.go +++ b/e2e/e2etests/helpers.go @@ -1,6 +1,8 @@ package e2etests import ( + "crypto/rand" + "encoding/hex" "math/big" "strconv" @@ -20,6 +22,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) +// randomPayload generates a random payload to be used in gateway calls for testing purposes +func randomPayload(r *runner.E2ERunner) string { + bytes := make([]byte, 50) + _, err := rand.Read(bytes) + require.NoError(r, err) + + return hex.EncodeToString(bytes) +} + func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) *btcjson.TxRawResult { tx, err := r.BTCZRC20.Approve( r.ZEVMAuth, diff --git a/e2e/e2etests/test_v2_erc20_deposit_and_call.go b/e2e/e2etests/test_v2_erc20_deposit_and_call.go index 332f2d6009..7b8215162a 100644 --- a/e2e/e2etests/test_v2_erc20_deposit_and_call.go +++ b/e2e/e2etests/test_v2_erc20_deposit_and_call.go @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) @@ -22,7 +20,9 @@ func TestV2ERC20DepositAndCall(r *runner.E2ERunner, args []string) { r.ApproveERC20OnEVM(r.GatewayEVMAddr) - r.AssertTestDAppZEVMCalled(false, payloadMessageDepositERC20, amount) + payload := randomPayload(r) + + r.AssertTestDAppZEVMCalled(false, payload, amount) oldBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) require.NoError(r, err) @@ -31,7 +31,7 @@ func TestV2ERC20DepositAndCall(r *runner.E2ERunner, args []string) { tx := r.V2ERC20DepositAndCall( r.TestDAppV2ZEVMAddr, amount, - []byte(payloadMessageDepositERC20), + []byte(payload), gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -41,7 +41,7 @@ func TestV2ERC20DepositAndCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppZEVMCalled(true, payloadMessageDepositERC20, amount) + r.AssertTestDAppZEVMCalled(true, payload, amount) // check the balance was updated newBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) 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 index 0bee6ff837..7f52c9e70f 100644 --- 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 @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) @@ -22,13 +20,15 @@ func TestV2ERC20DepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) r.ApproveERC20OnEVM(r.GatewayEVMAddr) - r.AssertTestDAppEVMCalled(false, payloadMessageDepositOnRevertERC20, amount) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) // perform the deposit tx := r.V2ERC20DepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ RevertAddress: r.TestDAppV2EVMAddr, CallOnRevert: true, - RevertMessage: []byte(payloadMessageDepositOnRevertERC20), + RevertMessage: []byte(payload), OnRevertGasLimit: big.NewInt(200000), }) @@ -38,12 +38,12 @@ func TestV2ERC20DepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppEVMCalled(true, payloadMessageDepositOnRevertERC20, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageDepositOnRevertERC20), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.EVMAuth.From, senderForMsg) diff --git a/e2e/e2etests/test_v2_erc20_withdraw_and_arbitrary_call.go b/e2e/e2etests/test_v2_erc20_withdraw_and_arbitrary_call.go index ca48f4be8b..6a73774f18 100644 --- a/e2e/e2etests/test_v2_erc20_withdraw_and_arbitrary_call.go +++ b/e2e/e2etests/test_v2_erc20_withdraw_and_arbitrary_call.go @@ -11,15 +11,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageWithdrawERC20 = "this is a test ERC20 withdraw and call payload" - func TestV2ERC20WithdrawAndArbitraryCall(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) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) r.ApproveERC20ZRC20(r.GatewayZEVMAddr) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -28,7 +28,7 @@ func TestV2ERC20WithdrawAndArbitraryCall(r *runner.E2ERunner, args []string) { tx := r.V2ERC20WithdrawAndArbitraryCall( r.TestDAppV2EVMAddr, amount, - r.EncodeERC20Call(r.ERC20Addr, amount, payloadMessageWithdrawERC20), + r.EncodeERC20Call(r.ERC20Addr, amount, payload), gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -37,5 +37,5 @@ func TestV2ERC20WithdrawAndArbitraryCall(r *runner.E2ERunner, args []string) { r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageWithdrawERC20, amount) + r.AssertTestDAppEVMCalled(true, payload, amount) } diff --git a/e2e/e2etests/test_v2_erc20_withdraw_and_call.go b/e2e/e2etests/test_v2_erc20_withdraw_and_call.go index 4596ba12da..fbc9e2ae7a 100644 --- a/e2e/e2etests/test_v2_erc20_withdraw_and_call.go +++ b/e2e/e2etests/test_v2_erc20_withdraw_and_call.go @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageWithdrawAuthenticatedCallERC20 = "this is a test ERC20 withdraw and authenticated call payload" - func TestV2ERC20WithdrawAndCall(r *runner.E2ERunner, _ []string) { previousGasLimit := r.ZEVMAuth.GasLimit r.ZEVMAuth.GasLimit = 10000000 @@ -25,7 +23,9 @@ func TestV2ERC20WithdrawAndCall(r *runner.E2ERunner, _ []string) { // without decoding the payload and amount handling for erc20, purpose of test is to verify correct sender and payload are used amount := big.NewInt(10000) - r.AssertTestDAppEVMCalled(false, payloadMessageWithdrawAuthenticatedCallERC20, amount) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) r.ApproveERC20ZRC20(r.GatewayZEVMAddr) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -34,7 +34,7 @@ func TestV2ERC20WithdrawAndCall(r *runner.E2ERunner, _ []string) { tx := r.V2ERC20WithdrawAndCall( r.TestDAppV2EVMAddr, amount, - []byte(payloadMessageWithdrawAuthenticatedCallERC20), + []byte(payload), gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -43,12 +43,12 @@ func TestV2ERC20WithdrawAndCall(r *runner.E2ERunner, _ []string) { r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageWithdrawAuthenticatedCallERC20, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageWithdrawAuthenticatedCallERC20), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.ZEVMAuth.From, senderForMsg) 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 index fb3b3201ff..b3d331f296 100644 --- 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 @@ -12,15 +12,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) + payload := randomPayload(r) + + r.AssertTestDAppZEVMCalled(false, payload, amount) r.ApproveERC20ZRC20(r.GatewayZEVMAddr) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -33,7 +33,7 @@ func TestV2ERC20WithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string gatewayzevm.RevertOptions{ RevertAddress: r.TestDAppV2ZEVMAddr, CallOnRevert: true, - RevertMessage: []byte(payloadMessageWithdrawOnRevertERC20), + RevertMessage: []byte(payload), OnRevertGasLimit: big.NewInt(0), }, ) @@ -43,12 +43,12 @@ func TestV2ERC20WithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) - r.AssertTestDAppZEVMCalled(true, payloadMessageWithdrawOnRevertERC20, big.NewInt(0)) + r.AssertTestDAppZEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2ZEVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageWithdrawOnRevertERC20), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.ZEVMAuth.From, senderForMsg) diff --git a/e2e/e2etests/test_v2_eth_deposit_and_call.go b/e2e/e2etests/test_v2_eth_deposit_and_call.go index a7b2a3a8be..67fbe73846 100644 --- a/e2e/e2etests/test_v2_eth_deposit_and_call.go +++ b/e2e/e2etests/test_v2_eth_deposit_and_call.go @@ -12,15 +12,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) + payload := randomPayload(r) + + r.AssertTestDAppZEVMCalled(false, payload, amount) oldBalance, err := r.ETHZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) require.NoError(r, err) @@ -29,7 +29,7 @@ func TestV2ETHDepositAndCall(r *runner.E2ERunner, args []string) { tx := r.V2ETHDepositAndCall( r.TestDAppV2ZEVMAddr, amount, - []byte(payloadMessageDepositETH), + []byte(payload), gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -39,7 +39,7 @@ func TestV2ETHDepositAndCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppZEVMCalled(true, payloadMessageDepositETH, amount) + r.AssertTestDAppZEVMCalled(true, payload, amount) // check the balance was updated newBalance, err := r.ETHZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) 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 index 245aa3e636..f01f2ce6e8 100644 --- 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 @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) @@ -22,13 +20,15 @@ func TestV2ETHDepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) { r.ApproveERC20OnEVM(r.GatewayEVMAddr) - r.AssertTestDAppEVMCalled(false, payloadMessageDepositOnRevertETH, amount) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) // perform the deposit tx := r.V2ETHDepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ RevertAddress: r.TestDAppV2EVMAddr, CallOnRevert: true, - RevertMessage: []byte(payloadMessageDepositOnRevertETH), + RevertMessage: []byte(payload), OnRevertGasLimit: big.NewInt(200000), }) @@ -38,12 +38,12 @@ func TestV2ETHDepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppEVMCalled(true, payloadMessageDepositOnRevertETH, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageDepositOnRevertETH), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.EVMAuth.From, senderForMsg) diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_arbitrary_call.go b/e2e/e2etests/test_v2_eth_withdraw_and_arbitrary_call.go index b290e33fcb..c3bedecb02 100644 --- a/e2e/e2etests/test_v2_eth_withdraw_and_arbitrary_call.go +++ b/e2e/e2etests/test_v2_eth_withdraw_and_arbitrary_call.go @@ -11,15 +11,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageWithdrawETH = "this is a test ETH withdraw and call payload" - func TestV2ETHWithdrawAndArbitraryCall(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) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -27,7 +27,7 @@ func TestV2ETHWithdrawAndArbitraryCall(r *runner.E2ERunner, args []string) { tx := r.V2ETHWithdrawAndArbitraryCall( r.TestDAppV2EVMAddr, amount, - r.EncodeGasCall(payloadMessageWithdrawETH), + r.EncodeGasCall(payload), gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -36,5 +36,5 @@ func TestV2ETHWithdrawAndArbitraryCall(r *runner.E2ERunner, args []string) { r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageWithdrawETH, amount) + r.AssertTestDAppEVMCalled(true, payload, amount) } diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call.go b/e2e/e2etests/test_v2_eth_withdraw_and_call.go index bffd037e72..9962bfe462 100644 --- a/e2e/e2etests/test_v2_eth_withdraw_and_call.go +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call.go @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageAuthenticatedWithdrawETH = "this is a test ETH withdraw and authenticated call payload" - func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) @@ -26,7 +24,9 @@ func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestV2ETHWithdrawAndCall") - r.AssertTestDAppEVMCalled(false, payloadMessageAuthenticatedWithdrawETH, amount) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, amount) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -34,7 +34,7 @@ func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { tx := r.V2ETHWithdrawAndCall( r.TestDAppV2EVMAddr, amount, - []byte(payloadMessageAuthenticatedWithdrawETH), + []byte(payload), gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -43,12 +43,12 @@ func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageAuthenticatedWithdrawETH, amount) + r.AssertTestDAppEVMCalled(true, payload, amount) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageAuthenticatedWithdrawETH), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.ZEVMAuth.From, senderForMsg) 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 index f613ebffc2..8c7ea40f9e 100644 --- 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 @@ -12,15 +12,15 @@ import ( crosschaintypes "github.com/zeta-chain/node/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) + payload := randomPayload(r) + + r.AssertTestDAppZEVMCalled(false, payload, amount) r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -32,7 +32,7 @@ func TestV2ETHWithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string) gatewayzevm.RevertOptions{ RevertAddress: r.TestDAppV2ZEVMAddr, CallOnRevert: true, - RevertMessage: []byte(payloadMessageWithdrawOnRevertETH), + RevertMessage: []byte(payload), OnRevertGasLimit: big.NewInt(0), }, ) @@ -42,12 +42,12 @@ func TestV2ETHWithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string) r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) - r.AssertTestDAppZEVMCalled(true, payloadMessageWithdrawOnRevertETH, big.NewInt(0)) + r.AssertTestDAppZEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2ZEVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageWithdrawOnRevertETH), + []byte(payload), ) require.NoError(r, err) require.Equal(r, r.ZEVMAuth.From, senderForMsg) diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call_through_contract.go b/e2e/e2etests/test_v2_eth_withdraw_and_call_through_contract.go index 28c36ee69d..ce82f95829 100644 --- a/e2e/e2etests/test_v2_eth_withdraw_and_call_through_contract.go +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call_through_contract.go @@ -12,8 +12,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageAuthenticatedWithdrawETHThroughContract = "this is a test ETH withdraw and authenticated call payload through contract" - func TestV2ETHWithdrawAndCallThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) @@ -40,10 +38,12 @@ func TestV2ETHWithdrawAndCallThroughContract(r *runner.E2ERunner, args []string) require.NoError(r, err) utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + payload := randomPayload(r) + // perform the authenticated call tx = r.V2ETHWithdrawAndCallThroughContract(gatewayCaller, r.TestDAppV2EVMAddr, amount, - []byte(payloadMessageAuthenticatedWithdrawETHThroughContract), + []byte(payload), gatewayzevmcaller.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) @@ -51,12 +51,12 @@ func TestV2ETHWithdrawAndCallThroughContract(r *runner.E2ERunner, args []string) r.Logger.CCTX(*cctx, "withdraw") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageAuthenticatedWithdrawETHThroughContract, amount) + r.AssertTestDAppEVMCalled(true, payload, amount) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageAuthenticatedWithdrawETHThroughContract), + []byte(payload), ) require.NoError(r, err) require.Equal(r, gatewayCallerAddr, senderForMsg) diff --git a/e2e/e2etests/test_v2_evm_to_zevm_call.go b/e2e/e2etests/test_v2_evm_to_zevm_call.go index 9fb5502e7a..305f279164 100644 --- a/e2e/e2etests/test_v2_evm_to_zevm_call.go +++ b/e2e/e2etests/test_v2_evm_to_zevm_call.go @@ -11,17 +11,17 @@ import ( crosschaintypes "github.com/zeta-chain/node/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)) + payload := randomPayload(r) + + r.AssertTestDAppZEVMCalled(false, payload, big.NewInt(0)) // perform the withdraw tx := r.V2EVMToZEMVCall( r.TestDAppV2ZEVMAddr, - []byte(payloadMessageZEVMCall), + []byte(payload), gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, ) @@ -31,5 +31,5 @@ func TestV2EVMToZEVMCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppZEVMCalled(true, payloadMessageZEVMCall, big.NewInt(0)) + r.AssertTestDAppZEVMCalled(true, payload, big.NewInt(0)) } diff --git a/e2e/e2etests/test_v2_zevm_to_evm_arbitrary_call.go b/e2e/e2etests/test_v2_zevm_to_evm_arbitrary_call.go index 4b722fc4ae..429fbea714 100644 --- a/e2e/e2etests/test_v2_zevm_to_evm_arbitrary_call.go +++ b/e2e/e2etests/test_v2_zevm_to_evm_arbitrary_call.go @@ -11,12 +11,12 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageEVMCall = "this is a test EVM call payload" - func TestV2ZEVMToEVMArbitraryCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 0) - r.AssertTestDAppEVMCalled(false, payloadMessageEVMCall, big.NewInt(0)) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, big.NewInt(0)) // Necessary approval for fee payment r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -24,7 +24,7 @@ func TestV2ZEVMToEVMArbitraryCall(r *runner.E2ERunner, args []string) { // perform the call tx := r.V2ZEVMToEMVArbitraryCall( r.TestDAppV2EVMAddr, - r.EncodeSimpleCall(payloadMessageEVMCall), + r.EncodeSimpleCall(payload), gatewayzevm.RevertOptions{ OnRevertGasLimit: big.NewInt(0), }, @@ -36,5 +36,5 @@ func TestV2ZEVMToEVMArbitraryCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppEVMCalled(true, payloadMessageEVMCall, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, 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 index 9641778e3c..2f1ed19d1b 100644 --- a/e2e/e2etests/test_v2_zevm_to_evm_call.go +++ b/e2e/e2etests/test_v2_zevm_to_evm_call.go @@ -12,12 +12,12 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageEVMAuthenticatedCall = "this is a test EVM authenticated call payload" - func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 0) - r.AssertTestDAppEVMCalled(false, payloadMessageEVMAuthenticatedCall, big.NewInt(0)) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, big.NewInt(0)) // necessary approval for fee payment r.ApproveETHZRC20(r.GatewayZEVMAddr) @@ -25,7 +25,7 @@ func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { // perform the authenticated call tx := r.V2ZEVMToEMVCall( r.TestDAppV2EVMAddr, - []byte(payloadMessageEVMAuthenticatedCall), + []byte(payload), gatewayzevm.RevertOptions{ OnRevertGasLimit: big.NewInt(0), }, @@ -37,10 +37,10 @@ func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) // check the payload was received on the contract - r.AssertTestDAppEVMCalled(true, payloadMessageEVMAuthenticatedCall, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used - senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage(&bind.CallOpts{}, []byte(payloadMessageEVMAuthenticatedCall)) + senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage(&bind.CallOpts{}, []byte(payload)) require.NoError(r, err) require.Equal(r, r.ZEVMAuth.From, senderForMsg) } diff --git a/e2e/e2etests/test_v2_zevm_to_evm_call_through_contract.go b/e2e/e2etests/test_v2_zevm_to_evm_call_through_contract.go index 7ff9158365..fa54ca34f6 100644 --- a/e2e/e2etests/test_v2_zevm_to_evm_call_through_contract.go +++ b/e2e/e2etests/test_v2_zevm_to_evm_call_through_contract.go @@ -12,12 +12,12 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -const payloadMessageEVMAuthenticatedCallThroughContract = "this is a test EVM authenticated call payload through contract" - func TestV2ZEVMToEVMCallThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 0) - r.AssertTestDAppEVMCalled(false, payloadMessageEVMAuthenticatedCallThroughContract, big.NewInt(0)) + payload := randomPayload(r) + + r.AssertTestDAppEVMCalled(false, payload, big.NewInt(0)) // deploy caller contract and send it gas zrc20 to pay gas fee gatewayCallerAddr, tx, gatewayCaller, err := gatewayzevmcaller.DeployGatewayZEVMCaller( @@ -37,7 +37,7 @@ func TestV2ZEVMToEVMCallThroughContract(r *runner.E2ERunner, args []string) { tx = r.V2ZEVMToEMVCallThroughContract( gatewayCaller, r.TestDAppV2EVMAddr, - []byte(payloadMessageEVMAuthenticatedCallThroughContract), + []byte(payload), gatewayzevmcaller.RevertOptions{ OnRevertGasLimit: big.NewInt(0), }, @@ -47,12 +47,12 @@ func TestV2ZEVMToEVMCallThroughContract(r *runner.E2ERunner, args []string) { r.Logger.CCTX(*cctx, "call") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - r.AssertTestDAppEVMCalled(true, payloadMessageEVMAuthenticatedCallThroughContract, big.NewInt(0)) + r.AssertTestDAppEVMCalled(true, payload, big.NewInt(0)) // check expected sender was used senderForMsg, err := r.TestDAppV2EVM.SenderWithMessage( &bind.CallOpts{}, - []byte(payloadMessageEVMAuthenticatedCallThroughContract), + []byte(payload), ) require.NoError(r, err) require.Equal(r, gatewayCallerAddr, senderForMsg) diff --git a/e2e/runner/v2_zevm.go b/e2e/runner/v2_zevm.go index 45dd8c6761..4f6f299db2 100644 --- a/e2e/runner/v2_zevm.go +++ b/e2e/runner/v2_zevm.go @@ -11,7 +11,7 @@ import ( gatewayzevmcaller "github.com/zeta-chain/node/pkg/contracts/gatewayzevmcaller" ) -var gasLimit = big.NewInt(1000000) +var gasLimit = big.NewInt(250000) // V2ETHWithdraw calls Withdraw of Gateway with gas token on ZEVM func (r *E2ERunner) V2ETHWithdraw( @@ -31,7 +31,7 @@ func (r *E2ERunner) V2ETHWithdraw( return tx } -// V2ETHWithdrawAndCall calls WithdrawAndCall of Gateway with gas token on ZEVM using arbitrary call +// V2ETHWithdrawAndArbitraryCall calls WithdrawAndCall of Gateway with gas token on ZEVM using arbitrary call func (r *E2ERunner) V2ETHWithdrawAndArbitraryCall( receiver ethcommon.Address, amount *big.Int, From 146f4435d835925069ade8af7b69ecf206086d84 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Thu, 31 Oct 2024 09:43:20 -0700 Subject: [PATCH 18/34] feat(e2e): add latency report to withdrawal performance tests (#3071) * feat(e2e): add latency report to withdrawal performance tests * update WaitCCTXMinedByIndex interval * address coderabbit --- e2e/e2etests/test_stress_eth_withdraw.go | 59 ++++++++++++++++-------- e2e/utils/zetacore.go | 10 ++-- go.mod | 1 + go.sum | 2 + 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/e2e/e2etests/test_stress_eth_withdraw.go b/e2e/e2etests/test_stress_eth_withdraw.go index 337a4d416d..864890d6cf 100644 --- a/e2e/e2etests/test_stress_eth_withdraw.go +++ b/e2e/e2etests/test_stress_eth_withdraw.go @@ -4,9 +4,10 @@ import ( "fmt" "math/big" "strconv" + "sync" "time" - ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/montanaflynn/stats" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -36,6 +37,10 @@ func TestStressEtherWithdraw(r *runner.E2ERunner, args []string) { // create a wait group to wait for all the withdraws to complete var eg errgroup.Group + // store durations as float64 seconds like prometheus + withdrawDurations := []float64{} + withdrawDurationsLock := sync.Mutex{} + // send the withdraws for i := 0; i < numWithdraws; i++ { i := i @@ -49,29 +54,45 @@ func TestStressEtherWithdraw(r *runner.E2ERunner, args []string) { r.Logger.Print("index %d: starting withdraw, tx hash: %s", i, tx.Hash().Hex()) eg.Go(func() error { - return monitorEtherWithdraw(r, tx, i, time.Now()) + startTime := time.Now() + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.ReceiptTimeout) + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + return fmt.Errorf( + "index %d: withdraw cctx failed with status %s, message %s, cctx index %s", + i, + cctx.CctxStatus.Status, + cctx.CctxStatus.StatusMessage, + cctx.Index, + ) + } + timeToComplete := time.Since(startTime) + r.Logger.Print("index %d: withdraw cctx success in %s", i, timeToComplete.String()) + + withdrawDurationsLock.Lock() + withdrawDurations = append(withdrawDurations, timeToComplete.Seconds()) + withdrawDurationsLock.Unlock() + + return nil }) } - require.NoError(r, eg.Wait()) + err = eg.Wait() - r.Logger.Print("all withdraws completed") -} + desc, descErr := stats.Describe(withdrawDurations, false, &[]float64{50.0, 75.0, 90.0, 95.0}) + if descErr != nil { + r.Logger.Print("❌ failed to calculate latency report: %v", descErr) + } -// monitorEtherWithdraw monitors the withdraw of ether, returns once the withdraw is complete -func monitorEtherWithdraw(r *runner.E2ERunner, tx *ethtypes.Transaction, index int, startTime time.Time) error { - cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.ReceiptTimeout) - if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { - return fmt.Errorf( - "index %d: withdraw cctx failed with status %s, message %s, cctx index %s", - index, - cctx.CctxStatus.Status, - cctx.CctxStatus.StatusMessage, - cctx.Index, - ) + r.Logger.Print("Latency report:") + r.Logger.Print("min: %.2f", desc.Min) + r.Logger.Print("max: %.2f", desc.Max) + r.Logger.Print("mean: %.2f", desc.Mean) + r.Logger.Print("std: %.2f", desc.Std) + for _, p := range desc.DescriptionPercentiles { + r.Logger.Print("p%.0f: %.2f", p.Percentile, p.Value) } - timeToComplete := time.Since(startTime) - r.Logger.Print("index %d: withdraw cctx success in %s", index, timeToComplete.String()) - return nil + require.NoError(r, err) + + r.Logger.Print("all withdraws completed") } diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 33f5d68262..0b4d5217ed 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -81,7 +81,7 @@ func WaitCctxsMinedByInboundHash( timedOut := time.Since(startTime) > timeout require.False(t, timedOut, "waiting cctx timeout, cctx not mined, inbound hash: %s", inboundHash) - time.Sleep(1 * time.Second) + time.Sleep(500 * time.Millisecond) // We use InTxHashToCctxData instead of InboundTrackerAllByChain to able to run these tests with the previous version // for the update tests @@ -90,7 +90,7 @@ func WaitCctxsMinedByInboundHash( res, err := client.InTxHashToCctxData(ctx, in) if err != nil { // prevent spamming logs - if i%10 == 0 { + if i%20 == 0 { logger.Info("Error getting cctx by inboundHash: %s", err.Error()) } continue @@ -113,7 +113,7 @@ func WaitCctxsMinedByInboundHash( cctx := cctx if !IsTerminalStatus(cctx.CctxStatus.Status) { // prevent spamming logs - if i%10 == 0 { + if i%20 == 0 { logger.Info( "waiting for cctx index %d to be mined by inboundHash: %s, cctx status: %s, message: %s", j, @@ -154,7 +154,7 @@ func WaitCCTXMinedByIndex( require.False(t, time.Since(startTime) > timeout, "waiting cctx timeout, cctx not mined, cctx: %s", cctxIndex) if i > 0 { - time.Sleep(1 * time.Second) + time.Sleep(500 * time.Millisecond) } // fetch cctx by index @@ -170,7 +170,7 @@ func WaitCCTXMinedByIndex( cctx := res.CrossChainTx if !IsTerminalStatus(cctx.CctxStatus.Status) { // prevent spamming logs - if i%10 == 0 { + if i%20 == 0 { logger.Info( "waiting for cctx to be mined from index: %s, cctx status: %s, message: %s", cctxIndex, diff --git a/go.mod b/go.mod index 8e769da887..bb5b93539c 100644 --- a/go.mod +++ b/go.mod @@ -340,6 +340,7 @@ require ( require ( github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index d2da2120e8..06040e5b57 100644 --- a/go.sum +++ b/go.sum @@ -3321,6 +3321,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= From b58046fcd4cf68fc769ec35f0b0caa9185c5b1fa Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 31 Oct 2024 22:40:30 +0100 Subject: [PATCH 19/34] refactor: improve ZETA deposit check with max supply check (#3074) * refactor: max supply check * create internal func --- testutil/keeper/mocks/fungible/bank.go | 18 +++++ x/fungible/keeper/deposits_test.go | 4 + x/fungible/keeper/zeta.go | 27 +++++++ x/fungible/keeper/zeta_test.go | 78 +++++++++++++++++++ .../keeper/zevm_message_passing_test.go | 6 ++ x/fungible/types/errors.go | 1 + x/fungible/types/expected_keepers.go | 1 + 7 files changed, 135 insertions(+) diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index db14226310..1c46b35688 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -13,6 +13,24 @@ type FungibleBankKeeper struct { mock.Mock } +// GetSupply provides a mock function with given fields: ctx, denom +func (_m *FungibleBankKeeper) GetSupply(ctx types.Context, denom string) types.Coin { + ret := _m.Called(ctx, denom) + + if len(ret) == 0 { + panic("no return value specified for GetSupply") + } + + var r0 types.Coin + if rf, ok := ret.Get(0).(func(types.Context, string) types.Coin); ok { + r0 = rf(ctx, denom) + } else { + r0 = ret.Get(0).(types.Coin) + } + + return r0 +} + // MintCoins provides a mock function with given fields: ctx, moduleName, amt func (_m *FungibleBankKeeper) MintCoins(ctx types.Context, moduleName string, amt types.Coins) error { ret := _m.Called(ctx, moduleName, amt) diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index 3958ae191e..836ce0b61a 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -437,6 +437,10 @@ func TestKeeper_DepositCoinZeta(t *testing.T) { b := sdkk.BankKeeper.GetBalance(ctx, zetaToAddress, config.BaseDenom) require.Equal(t, int64(0), b.Amount.Int64()) errorMint := errors.New("", 1, "error minting coins") + + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() err := k.DepositCoinZeta(ctx, to, amount) require.ErrorIs(t, err, errorMint) diff --git a/x/fungible/keeper/zeta.go b/x/fungible/keeper/zeta.go index bc15a06e44..cd4acfcaa7 100644 --- a/x/fungible/keeper/zeta.go +++ b/x/fungible/keeper/zeta.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,9 +10,17 @@ import ( "github.com/zeta-chain/node/x/fungible/types" ) +// ZETAMaxSupplyStr is the maximum mintable ZETA in the fungible module +// 1.85 billion ZETA +const ZETAMaxSupplyStr = "1850000000000000000000000000" + // MintZetaToEVMAccount mints ZETA (gas token) to the given address // NOTE: this method should be used with a temporary context, and it should not be committed if the method returns an error func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount *big.Int) error { + if err := k.validateZetaSupply(ctx, amount); err != nil { + return err + } + coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) // Mint coins if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { @@ -23,7 +32,25 @@ func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount } func (k *Keeper) MintZetaToFungibleModule(ctx sdk.Context, amount *big.Int) error { + if err := k.validateZetaSupply(ctx, amount); err != nil { + return err + } + coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) // Mint coins return k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) } + +// validateZetaSupply checks if the minted ZETA amount exceeds the maximum supply +func (k *Keeper) validateZetaSupply(ctx sdk.Context, amount *big.Int) error { + zetaMaxSupply, ok := sdk.NewIntFromString(ZETAMaxSupplyStr) + if !ok { + return fmt.Errorf("failed to parse ZETA max supply: %s", ZETAMaxSupplyStr) + } + + supply := k.bankKeeper.GetSupply(ctx, config.BaseDenom) + if supply.Amount.Add(sdk.NewIntFromBigInt(amount)).GT(zetaMaxSupply) { + return types.ErrMaxSupplyReached + } + return nil +} diff --git a/x/fungible/keeper/zeta_test.go b/x/fungible/keeper/zeta_test.go index 62e41700c1..51e5fe279c 100644 --- a/x/fungible/keeper/zeta_test.go +++ b/x/fungible/keeper/zeta_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "errors" + "github.com/stretchr/testify/mock" "math/big" "testing" @@ -11,6 +12,7 @@ import ( "github.com/zeta-chain/node/cmd/zetacored/config" testkeeper "github.com/zeta-chain/node/testutil/keeper" "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/fungible/keeper" "github.com/zeta-chain/node/x/fungible/types" ) @@ -29,6 +31,46 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { require.True(t, bal.Amount.Equal(sdk.NewInt(42))) }) + t.Run("mint the token to reach max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + acc := sample.Bech32AccAddress() + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply) + + err := k.MintZetaToEVMAccount(ctx, acc, newAmount.BigInt()) + require.NoError(t, err) + bal = sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.Amount.Equal(newAmount)) + }) + + t.Run("can't mint more than max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + acc := sample.Bech32AccAddress() + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply).Add(sdk.NewInt(1)) + + err := k.MintZetaToEVMAccount(ctx, acc, newAmount.BigInt()) + require.ErrorIs(t, err, types.ErrMaxSupplyReached) + }) + coins42 := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewInt(42))) t.Run("should fail if minting fail", func(t *testing.T) { @@ -36,6 +78,9 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper := testkeeper.GetFungibleBankMock(t, k) + mockBankKeeper.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() mockBankKeeper.On( "MintCoins", ctx, @@ -55,6 +100,9 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper := testkeeper.GetFungibleBankMock(t, k) + mockBankKeeper.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() mockBankKeeper.On( "MintCoins", ctx, @@ -76,3 +124,33 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper.AssertExpectations(t) }) } + +func TestKeeper_MintZetaToFungibleModule(t *testing.T) { + t.Run("should mint the token in the specified balance", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + acc := k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName).GetAddress() + + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + err := k.MintZetaToEVMAccount(ctx, acc, big.NewInt(42)) + require.NoError(t, err) + bal = sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.Amount.Equal(sdk.NewInt(42))) + }) + + t.Run("can't mint more than max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply).Add(sdk.NewInt(1)) + + err := k.MintZetaToFungibleModule(ctx, newAmount.BigInt()) + require.ErrorIs(t, err, types.ErrMaxSupplyReached) + }) +} diff --git a/x/fungible/keeper/zevm_message_passing_test.go b/x/fungible/keeper/zevm_message_passing_test.go index f68fec1f62..f8e1e300d2 100644 --- a/x/fungible/keeper/zevm_message_passing_test.go +++ b/x/fungible/keeper/zevm_message_passing_test.go @@ -146,6 +146,9 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { }) require.NoError(t, err) errorMint := errors.New("", 10, "error minting coins") + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() _, err = k.ZETADepositAndCallContract( @@ -296,6 +299,9 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { }) require.NoError(t, err) errorMint := errors.New("", 101, "error minting coins") + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() _, err = k.ZETARevertAndCallContract( diff --git a/x/fungible/types/errors.go b/x/fungible/types/errors.go index cf333c9545..cb152f7ffe 100644 --- a/x/fungible/types/errors.go +++ b/x/fungible/types/errors.go @@ -34,4 +34,5 @@ var ( ErrZRC20NilABI = cosmoserrors.Register(ModuleName, 1132, "ZRC20 ABI is nil") ErrZeroAddress = cosmoserrors.Register(ModuleName, 1133, "address cannot be zero") ErrInvalidAmount = cosmoserrors.Register(ModuleName, 1134, "invalid amount") + ErrMaxSupplyReached = cosmoserrors.Register(ModuleName, 1135, "max supply reached") ) diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index c8dc02f013..8af00293ed 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -33,6 +33,7 @@ type BankKeeper interface { amt sdk.Coins, ) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + GetSupply(ctx sdk.Context, denom string) sdk.Coin } type ObserverKeeper interface { From 2e5d79435b032d3f83e037bd0333303703dfe11c Mon Sep 17 00:00:00 2001 From: Tanmay Date: Thu, 31 Oct 2024 22:41:05 -0400 Subject: [PATCH 20/34] test: simulation import and export (#3033) * debug sim test * start modifuing for v50 * add sim test v1 * add sim export * move simulation tests to a separate directory * remove unused functions * add comments * enable gov module * enable gov module * fix lint error * add todos for BasicManager * add todos for BasicManager * add cleanup for TestFullAppSimulation * register legacy router * added a doc for simulation testing * undo db close for multi threaded test * add description for tests * remove go module from simulation tests * make basicsmanager private * Update Makefile Co-authored-by: Francisco de Borja Aranda Castillejo * Update changelog.md Co-authored-by: Francisco de Borja Aranda Castillejo * format simulation.md * add test for import export * test sim after import * add bench test * remove bench * add changelog * remove cosmos utils * remove print lines * add comments * ci: Add simulation tests workflow * merge ci workflow * Update .github/workflows/simulation-test.yaml Co-authored-by: semgrep-code-zeta-chain[bot] <181804379+semgrep-code-zeta-chain[bot]@users.noreply.github.com> * Update simulation-test.yaml Update simulation-test workflow * add branch sim-import-export to trigger ci run * format files * uncomment simulation tests from CI * fix defer functions * Rename sim tests file, add result aggregation and merge jobs * ignore simulation tests from codecov * Add runs on to matrix conditional job * add default make-targets * bump go * Update .github/workflows/sim.yaml workflow name Co-authored-by: Alex Gartner * Delete main branch from sim test workflow * improve formatting for simulation.md * move simulation tests to root directory * remove panic for Import Export Test * Trigger Sim Tests on Labeled PRs or when any file from x/**/* is changed * update sim.yaml * Update sim.yaml jobs needs * update sim.yaml * update codecov.yml * fixed some comments * remove directory sim * remove directory sim * format directory * remove utils file * add to codecov --------- Co-authored-by: Francisco de Borja Aranda Castillejo Co-authored-by: Julian Rubino Co-authored-by: semgrep-code-zeta-chain[bot] <181804379+semgrep-code-zeta-chain[bot]@users.noreply.github.com> Co-authored-by: Julian Rubino Co-authored-by: Alex Gartner --- .github/workflows/sim.yaml | 111 ++++ Makefile | 26 +- app/app.go | 4 + app/export.go | 12 +- changelog.md | 1 + codecov.yml | 1 + docs/development/SIMULATION_TESTING.md | 60 +- .../sim/sim_config.go => simulation/config.go | 2 +- .../sim_utils.go => simulation/simulation.go | 33 +- simulation/simulation_test.go | 541 ++++++++++++++++++ .../sim/sim_state.go => simulation/state.go | 2 +- tests/simulation/sim_test.go | 213 ------- 12 files changed, 775 insertions(+), 231 deletions(-) create mode 100644 .github/workflows/sim.yaml rename tests/simulation/sim/sim_config.go => simulation/config.go (99%) rename tests/simulation/sim/sim_utils.go => simulation/simulation.go (62%) create mode 100644 simulation/simulation_test.go rename tests/simulation/sim/sim_state.go => simulation/state.go (99%) delete mode 100644 tests/simulation/sim_test.go diff --git a/.github/workflows/sim.yaml b/.github/workflows/sim.yaml new file mode 100644 index 0000000000..11e6858a81 --- /dev/null +++ b/.github/workflows/sim.yaml @@ -0,0 +1,111 @@ +name: sim + +on: + push: + branches: + - develop + pull_request: + types: [opened, synchronize, labeled] + schedule: + - cron: "0 6 * * *" + workflow_dispatch: + inputs: + make-targets: + description: 'Comma separated list of make targets to run (e.g., test-sim-nondeterminism, test-sim-fullappsimulation)' + required: true + default: 'test-sim-nondeterminism' + +concurrency: + group: simulation-${{ github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + changed-files: + runs-on: ubuntu-latest + outputs: + modified_files: ${{ steps.changes.outputs.modified_files }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get changed files in x directory + id: changes + run: | + echo "::set-output name=modified_files::$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^x/' | xargs)" + + matrix-conditionals: + needs: changed-files + if: | + contains(github.event.pull_request.labels.*.name, 'SIM_TESTS') || needs.changed-files.outputs.modified_files + runs-on: ubuntu-22.04 + outputs: + SIM_TEST_NOND: ${{ steps.matrix-conditionals.outputs.SIM_TEST_NOND }} + SIM_TEST_FULL: ${{ steps.matrix-conditionals.outputs.SIM_TEST_FULL }} + SIM_TEST_IMPORT_EXPORT: ${{ steps.matrix-conditionals.outputs.SIM_TEST_IMPORT_EXPORT }} + SIM_TEST_AFTER_IMPORT: ${{ steps.matrix-conditionals.outputs.SIM_TEST_AFTER_IMPORT }} + steps: + - id: matrix-conditionals + uses: actions/github-script@v7 + with: + script: | + const makeTargetsInput = context.payload.inputs ? context.payload.inputs['make-targets'] : null; + const defaultTargets = ['test-sim-nondeterminism', 'test-sim-fullappsimulation', 'test-sim-import-export', 'test-sim-after-import']; + + const makeTargets = makeTargetsInput ? makeTargetsInput.split(',') : defaultTargets; + + core.setOutput('SIM_TEST_NOND', makeTargets.includes('test-sim-nondeterminism')); + core.setOutput('SIM_TEST_FULL', makeTargets.includes('test-sim-fullappsimulation')); + core.setOutput('SIM_TEST_IMPORT_EXPORT', makeTargets.includes('test-sim-import-export')); + core.setOutput('SIM_TEST_AFTER_IMPORT', makeTargets.includes('test-sim-after-import')); + + simulation-tests: + needs: + - matrix-conditionals + if: | + contains(github.event.pull_request.labels.*.name, 'SIM_TESTS') || needs.changed-files.outputs.modified_files + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - make-target: "test-sim-nondeterminism" + condition: ${{ needs.matrix-conditionals.outputs.SIM_TEST_NOND == 'true' }} + - make-target: "test-sim-fullappsimulation" + condition: ${{ needs.matrix-conditionals.outputs.SIM_TEST_FULL == 'true' }} + - make-target: "test-sim-import-export" + condition: ${{ needs.matrix-conditionals.outputs.SIM_TEST_IMPORT_EXPORT == 'true' }} + - make-target: "test-sim-after-import" + condition: ${{ needs.matrix-conditionals.outputs.SIM_TEST_AFTER_IMPORT == 'true' }} + name: ${{ matrix.make-target }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Install dependencies + run: make runsim + + - name: Run ${{ matrix.make-target }} + if: ${{ matrix.condition }} + run: | + make ${{ matrix.make-target }} + + sim-ok: + needs: + - simulation-tests + if: | + contains(github.event.pull_request.labels.*.name, 'SIM_TESTS') || needs.changed-files.outputs.modified_files + runs-on: ubuntu-22.04 + steps: + - name: Aggregate Results + run: | + result="${{ needs.simulation-tests.result }}" + if [[ $result == "success" || $result == "skipped" ]]; then + exit 0 + else + exit 1 + fi diff --git a/Makefile b/Makefile index 61b4c492e5..bcd233b12e 100644 --- a/Makefile +++ b/Makefile @@ -360,7 +360,7 @@ start-upgrade-import-mainnet-test: zetanode-upgrade ############################################################################### BINDIR ?= $(GOPATH)/bin -SIMAPP = ./tests/simulation +SIMAPP = ./simulation # Run sim is a cosmos tool which helps us to run multiple simulations in parallel. @@ -381,16 +381,22 @@ $(BINDIR)/runsim: # Period: Invariant check period # Timeout: Timeout for the simulation test define run-sim-test - @echo "Running $(1)..." + @echo "Running $(1)" @go test -mod=readonly $(SIMAPP) -run $(2) -Enabled=true \ -NumBlocks=$(3) -BlockSize=$(4) -Commit=true -Period=0 -v -timeout $(5) endef test-sim-nondeterminism: - $(call run-sim-test,"non-determinism test",TestAppStateDeterminism,100,200,2h) + $(call run-sim-test,"non-determinism test",TestAppStateDeterminism,100,200,30m) test-sim-fullappsimulation: - $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,2h) + $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) + +test-sim-import-export: + $(call run-sim-test,"test-import-export",TestAppImportExport,100,200,30m) + +test-sim-after-import: + $(call run-sim-test,"test-sim-after-import",TestAppSimulationAfterImport,100,200,30m) test-sim-multi-seed-long: runsim @echo "Running long multi-seed application simulation." @@ -400,13 +406,23 @@ test-sim-multi-seed-short: runsim @echo "Running short multi-seed application simulation." @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 10 TestFullAppSimulation +test-sim-import-export-long: runsim + @echo "Running application import/export simulation. This may take several minutes" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 500 50 TestAppImportExport +test-sim-after-import-long: runsim + @echo "Running application simulation-after-import. This may take several minute" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 500 50 TestAppSimulationAfterImport .PHONY: \ test-sim-nondeterminism \ test-sim-fullappsimulation \ test-sim-multi-seed-long \ -test-sim-multi-seed-short +test-sim-multi-seed-short \ +test-sim-import-export \ +test-sim-after-import \ +test-sim-import-export-long \ +test-sim-after-import-long ############################################################################### diff --git a/app/app.go b/app/app.go index 3a42d51aff..0fd9026348 100644 --- a/app/app.go +++ b/app/app.go @@ -1058,6 +1058,10 @@ func (app *App) BasicManager() module.BasicManager { return app.mb } +func (app *App) ModuleManager() *module.Manager { + return app.mm +} + func (app *App) BlockedAddrs() map[string]bool { blockList := make(map[string]bool) diff --git a/app/export.go b/app/export.go index 61bc52659b..5ccb887c5d 100644 --- a/app/export.go +++ b/app/export.go @@ -2,11 +2,13 @@ package app import ( "encoding/json" + "errors" "log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -75,7 +77,7 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []str // withdraw all validator commission app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { _, err := app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) - if err != nil { + if !errors.Is(err, distributiontypes.ErrNoValidatorCommission) && err != nil { panic(err) } return false @@ -161,7 +163,13 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []str counter := int16(0) for ; iter.Valid(); iter.Next() { - addr := sdk.ValAddress(iter.Key()[1:]) + key := iter.Key() + keyPrefixLength := 2 + if len(key) <= keyPrefixLength { + app.Logger().Error("unexpected key in staking store", "key", key) + continue + } + addr := sdk.ValAddress(key[keyPrefixLength:]) validator, found := app.StakingKeeper.GetValidator(ctx, addr) if !found { panic("expected validator, not found") diff --git a/changelog.md b/changelog.md index 4e58815e99..ce8df87a44 100644 --- a/changelog.md +++ b/changelog.md @@ -51,6 +51,7 @@ * [2894](https://github.com/zeta-chain/node/pull/2894) - increase gas limit for TSS vote tx * [2932](https://github.com/zeta-chain/node/pull/2932) - add gateway upgrade as part of the upgrade test * [2947](https://github.com/zeta-chain/node/pull/2947) - initialize simulation tests +* [3033](https://github.com/zeta-chain/node/pull/3033) - initialize simulation tests for import and export ### Fixes diff --git a/codecov.yml b/codecov.yml index fee85c9c04..da90e44bd9 100644 --- a/codecov.yml +++ b/codecov.yml @@ -81,3 +81,4 @@ ignore: - "precompiles/**/*.json" - "precompiles/**/*.sol" - "precompiles/**/*.gen.go" + - "simulation/*.go" diff --git a/docs/development/SIMULATION_TESTING.md b/docs/development/SIMULATION_TESTING.md index 1f5232911a..e6bb2f2ca7 100644 --- a/docs/development/SIMULATION_TESTING.md +++ b/docs/development/SIMULATION_TESTING.md @@ -1,34 +1,78 @@ # Zetachain simulation testing ## Overview The blockchain simulation tests how the blockchain application would behave under real life circumstances by generating -and sending randomized messages.The goal of this is to detect and debug failures that could halt a live chain,by providing -logs and statistics about the operations run by the simulator as well as exporting the latest application state. - +and sending randomized messages.The goal of this is to detect and debug failures that could halt a live chain by +providing logs and statistics about the operations run by the simulator as well as exporting the latest application +state. ## Simulation tests ### Nondeterminism test Nondeterminism test runs a full application simulation , and produces multiple blocks as per the config It checks the determinism of the application by comparing the apphash at the end of each run to other runs -The test certifies that , for the same set of operations ( irrespective of what the operations are ), we would reach the same final state if the initial state is the same +The test certifies that, for the same set of operations (regardless of what the operations are), we +would reach the same final state if the initial state is the same +Approximate run time is 2 minutes. ```bash make test-sim-nondeterminism ``` + ### Full application simulation test Full application runs a full app simulation test with the provided configuration. -At the end of the run it tries to export the genesis state to make sure the export works. +At the end of the run, it tries to export the genesis state to make sure the export works. +Approximate run time is 2 minutes. ```bash make test-sim-full-app ``` +### Import Export simulation test +The import export simulation test runs a full application simulation +and exports the application state at the end of the run. +This state is then imported into a new simulation. +At the end of the run, we compare the keys for the application state for both the simulations +to make sure they are the same. +Approximate run time is 2 minutes. +```bash +make test-sim-import-export +``` + +### Import and run simulation test +This simulation test exports the application state at the end of the run and imports it into a new simulation. +Approximate run time is 2 minutes. +```bash +make test-sim-after-import +``` + ### Multi seed long test -Multi seed long test runs a full application simulation with multiple seeds and multiple blocks.This runs the test for a longer duration compared to the multi seed short test +Multi seed long test runs a full application simulation with multiple seeds and multiple blocks. +It uses the `runsim` tool to run the same test in parallel threads. +Approximate run time is 30 minutes. ```bash make test-sim-multi-seed-long ``` ### Multi seed short test -Multi seed short test runs a full application simulation with multiple seeds and multiple blocks. This runs the test for a longer duration compared to the multi seed long test +Multi seed short test runs a full application simulation with multiple seeds and multiple blocks. +It uses the `runsim` tool to run the same test in parallel threads. +This test is a shorter version of the Multi seed long test. +Approximate run time is 10 minutes. ```bash make test-sim-multi-seed-short -``` \ No newline at end of file +``` + +### Import Export long test +This test runs the import export simulation test for a longer duration. +It uses the `runsim` tool to run the same test in parallel threads. +Approximate run time is 30 minutes. +```bash +make test-sim-import-export-long +``` + +### Import and run simulation test long +This test runs the import and run simulation test for a longer duration. +It uses the `runsim` tool to run the same test in parallel threads. +Approximate run time is 30 minutes. +```bash +make test-sim-after-import-long +``` + diff --git a/tests/simulation/sim/sim_config.go b/simulation/config.go similarity index 99% rename from tests/simulation/sim/sim_config.go rename to simulation/config.go index 8a6c281db8..254365c856 100644 --- a/tests/simulation/sim/sim_config.go +++ b/simulation/config.go @@ -1,4 +1,4 @@ -package sim +package simulation import ( "flag" diff --git a/tests/simulation/sim/sim_utils.go b/simulation/simulation.go similarity index 62% rename from tests/simulation/sim/sim_utils.go rename to simulation/simulation.go index b310d7cae2..faa727c65f 100644 --- a/tests/simulation/sim/sim_utils.go +++ b/simulation/simulation.go @@ -1,12 +1,16 @@ -package sim +package simulation import ( + "encoding/json" "fmt" + "os" dbm "github.com/cometbft/cometbft-db" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/zeta-chain/ethermint/app" evmante "github.com/zeta-chain/ethermint/app/ante" @@ -66,3 +70,30 @@ func PrintStats(db dbm.DB) { fmt.Println(db.Stats()["leveldb.stats"]) fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } + +// CheckExportSimulation exports the app state and simulation parameters to JSON +// if the export paths are defined. +func CheckExportSimulation(app runtime.AppI, config simtypes.Config, params simtypes.Params) error { + if config.ExportStatePath != "" { + exported, err := app.ExportAppStateAndValidators(false, nil, nil) + if err != nil { + return fmt.Errorf("failed to export app state: %w", err) + } + + if err := os.WriteFile(config.ExportStatePath, exported.AppState, 0o600); err != nil { + return err + } + } + + if config.ExportParamsPath != "" { + paramsBz, err := json.MarshalIndent(params, "", " ") + if err != nil { + return fmt.Errorf("failed to write app state to %s: %w", config.ExportStatePath, err) + } + + if err := os.WriteFile(config.ExportParamsPath, paramsBz, 0o600); err != nil { + return err + } + } + return nil +} diff --git a/simulation/simulation_test.go b/simulation/simulation_test.go new file mode 100644 index 0000000000..3f39c77fa1 --- /dev/null +++ b/simulation/simulation_test.go @@ -0,0 +1,541 @@ +package simulation_test + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "runtime/debug" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/node/app" + zetasimulation "github.com/zeta-chain/node/simulation" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" + cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" +) + +// AppChainID hardcoded chainID for simulation + +func init() { + zetasimulation.GetSimulatorFlags() +} + +type StoreKeysPrefixes struct { + A storetypes.StoreKey + B storetypes.StoreKey + Prefixes [][]byte +} + +const ( + SimAppChainID = "simulation_777-1" + SimBlockMaxGas = 815000000 + //github.com/zeta-chain/node/issues/3004 + // TODO : Support pebbleDB for simulation tests + SimDBBackend = "goleveldb" + SimDBName = "simulation" +) + +// interBlockCacheOpt returns a BaseApp option function that sets the persistent +// inter-block write-through cache. +func interBlockCacheOpt() func(*baseapp.BaseApp) { + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) +} + +// TestAppStateDeterminism runs a full application simulation , and produces multiple blocks as per the config +// It checks the determinism of the application by comparing the apphash at the end of each run to other runs +// The following test certifies that , for the same set of operations ( irrespective of what the operations are ) , +// we would reach the same final state if the initial state is the same +func TestAppStateDeterminism(t *testing.T) { + if !zetasimulation.FlagEnabledValue { + t.Skip("skipping application simulation") + } + + config := zetasimulation.NewConfigFromFlags() + + config.InitialBlockHeight = 1 + config.ExportParamsPath = "" + config.OnOperation = false + config.AllInvariants = false + config.ChainID = SimAppChainID + config.DBBackend = SimDBBackend + config.BlockMaxGas = SimBlockMaxGas + + numSeeds := 3 + numTimesToRunPerSeed := 5 + + // We will be overriding the random seed and just run a single simulation on the provided seed value + if config.Seed != cosmossimcli.DefaultSeedValue { + numSeeds = 1 + } + + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + + appOptions := make(cosmossimutils.AppOptionsMap, 0) + appOptions[server.FlagInvCheckPeriod] = zetasimulation.FlagPeriodValue + + t.Log("Running tests for numSeeds: ", numSeeds, " numTimesToRunPerSeed: ", numTimesToRunPerSeed) + + for i := 0; i < numSeeds; i++ { + if config.Seed == cosmossimcli.DefaultSeedValue { + config.Seed = rand.Int63() + } + // For the same seed, the simApp hash produced at the end of each run should be the same + for j := 0; j < numTimesToRunPerSeed; j++ { + db, dir, logger, _, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend, + SimDBName, + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + require.NoError(t, err) + appOptions[flags.FlagHome] = dir + + simApp, err := zetasimulation.NewSimApp( + logger, + db, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + + t.Logf( + "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + ) + + blockedAddresses := simApp.ModuleAccountAddrs() + + // Random seed is used to produce a random initial state for the simulation + _, _, err = simulation.SimulateFromSeed( + t, + os.Stdout, + simApp.BaseApp, + zetasimulation.AppStateFn( + simApp.AppCodec(), + simApp.SimulationManager(), + simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + ), + cosmossim.RandomAccounts, + cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), + blockedAddresses, + config, + simApp.AppCodec(), + ) + require.NoError(t, err) + + zetasimulation.PrintStats(db) + + appHash := simApp.LastCommitID().Hash + appHashList[j] = appHash + + // Clean up resources + t.Cleanup(func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }) + + if j != 0 { + require.Equal( + t, + string(appHashList[0]), + string(appHashList[j]), + "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, + i+1, + numSeeds, + j+1, + numTimesToRunPerSeed, + ) + } + } + } +} + +// TestFullAppSimulation runs a full simApp simulation with the provided configuration. +// At the end of the run it tries to export the genesis state to make sure the export works. +func TestFullAppSimulation(t *testing.T) { + + config := zetasimulation.NewConfigFromFlags() + + config.ChainID = SimAppChainID + config.BlockMaxGas = SimBlockMaxGas + config.DBBackend = SimDBBackend + + db, dir, logger, skip, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend, + SimDBName, + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + t.Cleanup(func() { + if err := db.Close(); err != nil { + require.NoError(t, err, "Error closing new database") + } + if err := os.RemoveAll(dir); err != nil { + require.NoError(t, err, "Error removing directory") + } + }) + appOptions := make(cosmossimutils.AppOptionsMap, 0) + appOptions[server.FlagInvCheckPeriod] = zetasimulation.FlagPeriodValue + appOptions[flags.FlagHome] = dir + + simApp, err := zetasimulation.NewSimApp( + logger, + db, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + require.NoError(t, err) + + blockedAddresses := simApp.ModuleAccountAddrs() + _, _, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + simApp.BaseApp, + zetasimulation.AppStateFn( + simApp.AppCodec(), + simApp.SimulationManager(), + simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + ), + cosmossim.RandomAccounts, + cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), + blockedAddresses, + config, + simApp.AppCodec(), + ) + require.NoError(t, simErr) + + // check export works as expected + exported, err := simApp.ExportAppStateAndValidators(false, nil, nil) + require.NoError(t, err) + if config.ExportStatePath != "" { + err := os.WriteFile(config.ExportStatePath, exported.AppState, 0o600) + require.NoError(t, err) + } + + zetasimulation.PrintStats(db) +} + +func TestAppImportExport(t *testing.T) { + config := zetasimulation.NewConfigFromFlags() + + config.ChainID = SimAppChainID + config.BlockMaxGas = SimBlockMaxGas + config.DBBackend = SimDBBackend + + db, dir, logger, skip, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend, + SimDBName, + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + t.Cleanup(func() { + if err := db.Close(); err != nil { + require.NoError(t, err, "Error closing new database") + } + if err := os.RemoveAll(dir); err != nil { + require.NoError(t, err, "Error removing directory") + } + }) + + appOptions := make(cosmossimutils.AppOptionsMap, 0) + appOptions[server.FlagInvCheckPeriod] = zetasimulation.FlagPeriodValue + appOptions[flags.FlagHome] = dir + simApp, err := zetasimulation.NewSimApp( + logger, + db, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + require.NoError(t, err) + + // Run randomized simulation + blockedAddresses := simApp.ModuleAccountAddrs() + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + simApp.BaseApp, + zetasimulation.AppStateFn( + simApp.AppCodec(), + simApp.SimulationManager(), + simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + ), + cosmossim.RandomAccounts, + cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), + blockedAddresses, + config, + simApp.AppCodec(), + ) + require.NoError(t, simErr) + + err = zetasimulation.CheckExportSimulation(simApp, config, simParams) + require.NoError(t, err) + + zetasimulation.PrintStats(db) + + t.Log("exporting genesis") + // export state and simParams + exported, err := simApp.ExportAppStateAndValidators(false, []string{}, []string{}) + require.NoError(t, err) + + t.Log("importing genesis") + newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend+"_new", + SimDBName+"_new", + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + + require.NoError(t, err, "simulation setup failed") + + t.Cleanup(func() { + if err := newDB.Close(); err != nil { + require.NoError(t, err, "Error closing new database") + } + if err := os.RemoveAll(newDir); err != nil { + require.NoError(t, err, "Error removing directory") + } + }) + + newSimApp, err := zetasimulation.NewSimApp( + logger, + newDB, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + require.NoError(t, err) + + var genesisState app.GenesisState + err = json.Unmarshal(exported.AppState, &genesisState) + require.NoError(t, err) + + defer func() { + if r := recover(); r != nil { + err := fmt.Sprintf("%v", r) + require.Contains(t, err, "validator set is empty after InitGenesis", "unexpected error: %v", r) + t.Log("Skipping simulation as all validators have been unbonded") + t.Log("err", err, "stacktrace", string(debug.Stack())) + } + }() + + // Create context for the old and the new sim app, which can be used to compare keys + ctxSimApp := simApp.NewContext(true, tmproto.Header{ + Height: simApp.LastBlockHeight(), + ChainID: SimAppChainID, + }).WithChainID(SimAppChainID) + ctxNewSimApp := newSimApp.NewContext(true, tmproto.Header{ + Height: simApp.LastBlockHeight(), + ChainID: SimAppChainID, + }).WithChainID(SimAppChainID) + + // Use genesis state from the first app to initialize the second app + newSimApp.ModuleManager().InitGenesis(ctxNewSimApp, newSimApp.AppCodec(), genesisState) + newSimApp.StoreConsensusParams(ctxNewSimApp, exported.ConsensusParams) + + t.Log("comparing stores") + // The ordering of the keys is not important, we compare the same prefix for both simulations + storeKeysPrefixes := []StoreKeysPrefixes{ + {simApp.GetKey(authtypes.StoreKey), newSimApp.GetKey(authtypes.StoreKey), [][]byte{}}, + { + simApp.GetKey(stakingtypes.StoreKey), newSimApp.GetKey(stakingtypes.StoreKey), + [][]byte{ + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, + stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, + }, + }, + {simApp.GetKey(slashingtypes.StoreKey), newSimApp.GetKey(slashingtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(distrtypes.StoreKey), newSimApp.GetKey(distrtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(banktypes.StoreKey), newSimApp.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, + {simApp.GetKey(paramtypes.StoreKey), newSimApp.GetKey(paramtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(govtypes.StoreKey), newSimApp.GetKey(govtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(evidencetypes.StoreKey), newSimApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, + {simApp.GetKey(evmtypes.StoreKey), newSimApp.GetKey(evmtypes.StoreKey), [][]byte{}}, + } + + for _, skp := range storeKeysPrefixes { + storeA := ctxSimApp.KVStore(skp.A) + storeB := ctxNewSimApp.KVStore(skp.B) + + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal( + t, + 0, + len(failedKVAs), + cosmossimutils.GetSimulationLog( + skp.A.Name(), + simApp.SimulationManager().StoreDecoders, + failedKVAs, + failedKVBs, + ), + ) + } +} + +func TestAppSimulationAfterImport(t *testing.T) { + config := zetasimulation.NewConfigFromFlags() + + config.ChainID = SimAppChainID + config.BlockMaxGas = SimBlockMaxGas + config.DBBackend = SimDBBackend + + db, dir, logger, skip, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend, + SimDBName, + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + t.Cleanup(func() { + if err := db.Close(); err != nil { + require.NoError(t, err, "Error closing new database") + } + if err := os.RemoveAll(dir); err != nil { + require.NoError(t, err, "Error removing directory") + } + }) + + appOptions := make(cosmossimutils.AppOptionsMap, 0) + appOptions[server.FlagInvCheckPeriod] = zetasimulation.FlagPeriodValue + appOptions[flags.FlagHome] = dir + simApp, err := zetasimulation.NewSimApp( + logger, + db, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + require.NoError(t, err) + + // Run randomized simulation + blockedAddresses := simApp.ModuleAccountAddrs() + stopEarly, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + simApp.BaseApp, + zetasimulation.AppStateFn( + simApp.AppCodec(), + simApp.SimulationManager(), + simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + ), + cosmossim.RandomAccounts, + cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), + blockedAddresses, + config, + simApp.AppCodec(), + ) + require.NoError(t, simErr) + + err = zetasimulation.CheckExportSimulation(simApp, config, simParams) + require.NoError(t, err) + + zetasimulation.PrintStats(db) + + if stopEarly { + t.Log("can't export or import a zero-validator genesis, exiting test") + return + } + + t.Log("exporting genesis") + + // export state and simParams + exported, err := simApp.ExportAppStateAndValidators(true, []string{}, []string{}) + require.NoError(t, err) + + t.Log("importing genesis") + + newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( + config, + SimDBBackend+"_new", + SimDBName+"_new", + zetasimulation.FlagVerboseValue, + zetasimulation.FlagEnabledValue, + ) + + require.NoError(t, err, "simulation setup failed") + + t.Cleanup(func() { + if err := newDB.Close(); err != nil { + require.NoError(t, err, "Error closing new database") + } + if err := os.RemoveAll(newDir); err != nil { + require.NoError(t, err, "Error removing directory") + } + }) + + newSimApp, err := zetasimulation.NewSimApp( + logger, + newDB, + appOptions, + interBlockCacheOpt(), + baseapp.SetChainID(SimAppChainID), + ) + require.NoError(t, err) + + newSimApp.InitChain(abci.RequestInitChain{ + ChainId: SimAppChainID, + AppStateBytes: exported.AppState, + }) + + stopEarly, simParams, simErr = simulation.SimulateFromSeed( + t, + os.Stdout, + newSimApp.BaseApp, + zetasimulation.AppStateFn( + simApp.AppCodec(), + simApp.SimulationManager(), + simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + ), + cosmossim.RandomAccounts, + cosmossimutils.SimulationOperations(newSimApp, newSimApp.AppCodec(), config), + blockedAddresses, + config, + simApp.AppCodec(), + ) + require.NoError(t, err) +} diff --git a/tests/simulation/sim/sim_state.go b/simulation/state.go similarity index 99% rename from tests/simulation/sim/sim_state.go rename to simulation/state.go index c8df9f4f31..540cd0aca7 100644 --- a/tests/simulation/sim/sim_state.go +++ b/simulation/state.go @@ -1,4 +1,4 @@ -package sim +package simulation import ( "encoding/json" diff --git a/tests/simulation/sim_test.go b/tests/simulation/sim_test.go deleted file mode 100644 index 957e92081a..0000000000 --- a/tests/simulation/sim_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package simulation_test - -import ( - "encoding/json" - "math/rand" - "os" - "testing" - - "github.com/stretchr/testify/require" - simutils "github.com/zeta-chain/node/tests/simulation/sim" - - "github.com/cosmos/cosmos-sdk/store" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" - cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" - cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" -) - -// AppChainID hardcoded chainID for simulation - -func init() { - simutils.GetSimulatorFlags() -} - -const ( - SimAppChainID = "simulation_777-1" - SimBlockMaxGas = 815000000 - //github.com/zeta-chain/node/issues/3004 - // TODO : Support pebbleDB for simulation tests - SimDBBackend = "goleveldb" - SimDBName = "simulation" -) - -// interBlockCacheOpt returns a BaseApp option function that sets the persistent -// inter-block write-through cache. -func interBlockCacheOpt() func(*baseapp.BaseApp) { - return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) -} - -// TestAppStateDeterminism runs a full application simulation , and produces multiple blocks as per the config -// It checks the determinism of the application by comparing the apphash at the end of each run to other runs -// The following test certifies that , for the same set of operations ( irrespective of what the operations are ) , -// we would reach the same final state if the initial state is the same -func TestAppStateDeterminism(t *testing.T) { - if !simutils.FlagEnabledValue { - t.Skip("skipping application simulation") - } - - config := simutils.NewConfigFromFlags() - - config.InitialBlockHeight = 1 - config.ExportParamsPath = "" - config.OnOperation = false - config.AllInvariants = false - config.ChainID = SimAppChainID - config.DBBackend = SimDBBackend - config.BlockMaxGas = SimBlockMaxGas - - numSeeds := 3 - numTimesToRunPerSeed := 5 - - // We will be overriding the random seed and just run a single simulation on the provided seed value - if config.Seed != cosmossimcli.DefaultSeedValue { - numSeeds = 1 - } - - appHashList := make([]json.RawMessage, numTimesToRunPerSeed) - - appOptions := make(cosmossimutils.AppOptionsMap, 0) - appOptions[server.FlagInvCheckPeriod] = simutils.FlagPeriodValue - - t.Log("Running tests for numSeeds: ", numSeeds, " numTimesToRunPerSeed: ", numTimesToRunPerSeed) - - for i := 0; i < numSeeds; i++ { - if config.Seed == cosmossimcli.DefaultSeedValue { - config.Seed = rand.Int63() - } - // For the same seed, the app hash produced at the end of each run should be the same - for j := 0; j < numTimesToRunPerSeed; j++ { - db, dir, logger, _, err := cosmossimutils.SetupSimulation( - config, - SimDBBackend, - SimDBName, - simutils.FlagVerboseValue, - simutils.FlagEnabledValue, - ) - require.NoError(t, err) - appOptions[flags.FlagHome] = dir - - simApp, err := simutils.NewSimApp( - logger, - db, - appOptions, - interBlockCacheOpt(), - baseapp.SetChainID(SimAppChainID), - ) - - t.Logf( - "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", - config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, - ) - - blockedAddresses := simApp.ModuleAccountAddrs() - - // Random seed is used to produce a random initial state for the simulation - _, _, err = simulation.SimulateFromSeed( - t, - os.Stdout, - simApp.BaseApp, - simutils.AppStateFn( - simApp.AppCodec(), - simApp.SimulationManager(), - simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), - ), - cosmossim.RandomAccounts, - cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), - blockedAddresses, - config, - simApp.AppCodec(), - ) - require.NoError(t, err) - - simutils.PrintStats(db) - - appHash := simApp.LastCommitID().Hash - appHashList[j] = appHash - - // Clean up resources - require.NoError(t, db.Close()) - require.NoError(t, os.RemoveAll(dir)) - - if j != 0 { - require.Equal( - t, - string(appHashList[0]), - string(appHashList[j]), - "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", - config.Seed, - i+1, - numSeeds, - j+1, - numTimesToRunPerSeed, - ) - } - } - } -} - -// TestFullAppSimulation runs a full app simulation with the provided configuration. -// At the end of the run it tries to export the genesis state to make sure the export works. -func TestFullAppSimulation(t *testing.T) { - - config := simutils.NewConfigFromFlags() - - config.ChainID = SimAppChainID - config.BlockMaxGas = SimBlockMaxGas - config.DBBackend = SimDBBackend - - db, dir, logger, skip, err := cosmossimutils.SetupSimulation( - config, - SimDBBackend, - SimDBName, - simutils.FlagVerboseValue, - simutils.FlagEnabledValue, - ) - if skip { - t.Skip("skipping application simulation") - } - require.NoError(t, err, "simulation setup failed") - - defer func() { - require.NoError(t, db.Close()) - require.NoError(t, os.RemoveAll(dir)) - }() - appOptions := make(cosmossimutils.AppOptionsMap, 0) - appOptions[server.FlagInvCheckPeriod] = simutils.FlagPeriodValue - appOptions[flags.FlagHome] = dir - - simApp, err := simutils.NewSimApp(logger, db, appOptions, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID)) - require.NoError(t, err) - - blockedAddresses := simApp.ModuleAccountAddrs() - _, _, simerr := simulation.SimulateFromSeed( - t, - os.Stdout, - simApp.BaseApp, - simutils.AppStateFn( - simApp.AppCodec(), - simApp.SimulationManager(), - simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), - ), - cosmossim.RandomAccounts, - cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), - blockedAddresses, - config, - simApp.AppCodec(), - ) - require.NoError(t, simerr) - - // check export works as expected - exported, err := simApp.ExportAppStateAndValidators(false, nil, nil) - require.NoError(t, err) - if config.ExportStatePath != "" { - err := os.WriteFile(config.ExportStatePath, exported.AppState, 0o600) - require.NoError(t, err) - } - - simutils.PrintStats(db) -} From f9dab52cd578729a55e978e2ed8239c9fd7807ba Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Fri, 1 Nov 2024 02:48:48 -0700 Subject: [PATCH 21/34] chore(e2e): use 2 spaces for yaml (#3070) * e2e: use 2 spaces for yaml * fixes * address feedback --- cmd/zetae2e/init.go | 20 +++++++++++--------- e2e/config/config.go | 15 +++++++++++---- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cmd/zetae2e/init.go b/cmd/zetae2e/init.go index d8dbfd379f..6f98c24102 100644 --- a/cmd/zetae2e/init.go +++ b/cmd/zetae2e/init.go @@ -9,7 +9,7 @@ import ( "github.com/zeta-chain/node/e2e/config" ) -var initConf = config.Config{} +var initConf = config.DefaultConfig() var configFile = "" func NewInitCmd() *cobra.Command { @@ -19,18 +19,20 @@ func NewInitCmd() *cobra.Command { RunE: initConfig, } - InitCmd.Flags().StringVar(&initConf.RPCs.EVM, "ethURL", "http://eth:8545", "--ethURL http://eth:8545") - InitCmd.Flags().StringVar(&initConf.RPCs.ZetaCoreGRPC, "grpcURL", "zetacore0:9090", "--grpcURL zetacore0:9090") + InitCmd.Flags().StringVar(&initConf.RPCs.EVM, "ethURL", initConf.RPCs.EVM, "--ethURL http://eth:8545") InitCmd.Flags(). - StringVar(&initConf.RPCs.ZetaCoreRPC, "rpcURL", "http://zetacore0:26657", "--rpcURL http://zetacore0:26657") + StringVar(&initConf.RPCs.ZetaCoreGRPC, "grpcURL", initConf.RPCs.ZetaCoreGRPC, "--grpcURL zetacore0:9090") InitCmd.Flags(). - StringVar(&initConf.RPCs.Zevm, "zevmURL", "http://zetacore0:8545", "--zevmURL http://zetacore0:8545") - InitCmd.Flags().StringVar(&initConf.RPCs.Bitcoin.Host, "btcURL", "bitcoin:18443", "--grpcURL bitcoin:18443") + StringVar(&initConf.RPCs.ZetaCoreRPC, "rpcURL", initConf.RPCs.ZetaCoreRPC, "--rpcURL http://zetacore0:26657") InitCmd.Flags(). - StringVar(&initConf.RPCs.Solana, "solanaURL", "http://solana:8899", "--solanaURL http://solana:8899") + StringVar(&initConf.RPCs.Zevm, "zevmURL", initConf.RPCs.Zevm, "--zevmURL http://zetacore0:8545") InitCmd.Flags(). - StringVar(&initConf.RPCs.TONSidecarURL, "tonSidecarURL", "http://ton:8000", "--tonSidecarURL http://ton:8000") - InitCmd.Flags().StringVar(&initConf.ZetaChainID, "chainID", "athens_101-1", "--chainID athens_101-1") + StringVar(&initConf.RPCs.Bitcoin.Host, "btcURL", initConf.RPCs.Bitcoin.Host, "--btcURL bitcoin:18443") + InitCmd.Flags(). + StringVar(&initConf.RPCs.Solana, "solanaURL", initConf.RPCs.Solana, "--solanaURL http://solana:8899") + InitCmd.Flags(). + StringVar(&initConf.RPCs.TONSidecarURL, "tonSidecarURL", initConf.RPCs.TONSidecarURL, "--tonSidecarURL http://ton:8000") + InitCmd.Flags().StringVar(&initConf.ZetaChainID, "chainID", initConf.ZetaChainID, "--chainID athens_101-1") InitCmd.Flags().StringVar(&configFile, local.FlagConfigFile, "e2e.config", "--cfg ./e2e.config") return InitCmd diff --git a/e2e/config/config.go b/e2e/config/config.go index 32a799c027..67685b0dd3 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -208,13 +208,20 @@ func WriteConfig(file string, config Config) error { return errors.New("file name cannot be empty") } - b, err := yaml.Marshal(config) + // #nosec G304 -- the variable is expected to be controlled by the user + fHandle, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - return err + return fmt.Errorf("open file: %w", err) } - err = os.WriteFile(file, b, 0600) + defer fHandle.Close() + + // use a custom encoder so we can set the indentation level + encoder := yaml.NewEncoder(fHandle) + defer encoder.Close() + encoder.SetIndent(2) + err = encoder.Encode(config) if err != nil { - return err + return fmt.Errorf("encode config: %w", err) } return nil } From 6eb85e3a70516213b20ab44b21b48b5861c9d0ef Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Fri, 1 Nov 2024 09:19:38 -0700 Subject: [PATCH 22/34] refactor(e2e): setup zrc20 in one transaction (#3077) --- cmd/zetae2e/local/local.go | 6 +- e2e/e2etests/test_whitelist_erc20.go | 2 +- e2e/txserver/zeta_tx_server.go | 222 +++++++++++++-------------- 3 files changed, 115 insertions(+), 115 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 7cde1a92ad..42ca736de0 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -185,6 +185,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { // set the authority client to the zeta tx server to be able to query message permissions deployerRunner.ZetaTxServer.SetAuthorityClient(deployerRunner.AuthorityClient) + // run setup steps that do not require tss + if !skipSetup { + noError(deployerRunner.FundEmissionsPool()) + } + // wait for keygen to be completed // if setup is skipped, we assume that the keygen is already completed if !skipSetup { @@ -229,7 +234,6 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if testSolana { deployerRunner.SetupSolana(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String()) } - noError(deployerRunner.FundEmissionsPool()) deployerRunner.MintERC20OnEvm(1000000) diff --git a/e2e/e2etests/test_whitelist_erc20.go b/e2e/e2etests/test_whitelist_erc20.go index df34b05597..efd24ef16e 100644 --- a/e2e/e2etests/test_whitelist_erc20.go +++ b/e2e/e2etests/test_whitelist_erc20.go @@ -50,7 +50,7 @@ func TestWhitelistERC20(r *runner.E2ERunner, _ []string) { erc20zrc20Addr, err := txserver.FetchAttributeFromTxResponse(res, "zrc20_address") require.NoError(r, err) - err = r.ZetaTxServer.InitializeLiquidityCap(erc20zrc20Addr) + err = r.ZetaTxServer.InitializeLiquidityCaps(erc20zrc20Addr) require.NoError(r, err) // ensure CCTX created diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index a79c5af098..c86f291432 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -11,6 +11,7 @@ import ( "strings" "time" + abci "github.com/cometbft/cometbft/abci/types" rpchttp "github.com/cometbft/cometbft/rpc/client/http" coretypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" @@ -32,7 +33,7 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/samber/lo" "github.com/zeta-chain/ethermint/crypto/hd" etherminttypes "github.com/zeta-chain/ethermint/types" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" @@ -192,7 +193,7 @@ func (zts ZetaTxServer) GetAccountMnemonic(index int) string { // BroadcastTx broadcasts a tx to ZetaChain with the provided msg from the account // and waiting for blockTime for tx to be included in the block -func (zts ZetaTxServer) BroadcastTx(account string, msg sdktypes.Msg) (*sdktypes.TxResponse, error) { +func (zts ZetaTxServer) BroadcastTx(account string, msgs ...sdktypes.Msg) (*sdktypes.TxResponse, error) { // Find number and sequence and set it acc, err := zts.clientCtx.Keyring.Key(account) if err != nil { @@ -208,10 +209,13 @@ func (zts ZetaTxServer) BroadcastTx(account string, msg sdktypes.Msg) (*sdktypes } zts.txFactory = zts.txFactory.WithAccountNumber(accountNumber).WithSequence(accountSeq) - txBuilder, err := zts.txFactory.BuildUnsignedTx(msg) + txBuilder, err := zts.txFactory.BuildUnsignedTx(msgs...) if err != nil { return nil, err } + // increase gas and fees if multiple messages are provided + txBuilder.SetGasLimit(zts.txFactory.Gas() * uint64(len(msgs))) + txBuilder.SetFeeAmount(zts.txFactory.Fees().MulInt(sdktypes.NewInt(int64(len(msgs))))) // Sign tx err = tx.Sign(zts.txFactory, account, txBuilder, true) @@ -237,6 +241,9 @@ func broadcastWithBlockTimeout(zts ZetaTxServer, txBytes []byte) (*sdktypes.TxRe TxHash: res.TxHash, }, err } + if res.Code != 0 { + return res, fmt.Errorf("broadcast failed: %s", res.RawLog) + } exitAfter := time.After(zts.blockTimeout) hash, err := hex.DecodeString(res.TxHash) @@ -261,6 +268,9 @@ func mkTxResult( clientCtx client.Context, resTx *coretypes.ResultTx, ) (*sdktypes.TxResponse, error) { + if resTx.TxResult.Code != 0 { + return nil, fmt.Errorf("tx failed: %s", resTx.TxResult.Log) + } txb, err := clientCtx.TxConfig.TxDecoder()(resTx.Tx) if err != nil { return nil, err @@ -442,105 +452,102 @@ func (zts ZetaTxServer) DeployZRC20s( deployerAddr = addrOperational.String() } - deploy := func(msg *fungibletypes.MsgDeployFungibleCoinZRC20) (string, error) { - // noop - if skipChain(msg.ForeignChainId) { - return "", nil - } - - res, err := zts.BroadcastTx(deployerAccount, msg) - if err != nil { - return "", fmt.Errorf("failed to deploy eth zrc20: %w", err) - } - - addr, err := fetchZRC20FromDeployResponse(res) - if err != nil { - return "", fmt.Errorf("unable to fetch zrc20 from deploy response: %w", err) - } - - if err := zts.InitializeLiquidityCap(addr); err != nil { - return "", fmt.Errorf("unable to initialize liquidity cap: %w", err) - } - - return addr, nil - } + deployMsgs := []*fungibletypes.MsgDeployFungibleCoinZRC20{ + fungibletypes.NewMsgDeployFungibleCoinZRC20( + deployerAddr, + "", + chains.GoerliLocalnet.ChainId, + 18, + "ETH", + "gETH", + coin.CoinType_Gas, + 100000, + ), + fungibletypes.NewMsgDeployFungibleCoinZRC20( + deployerAddr, + "", + chains.BitcoinRegtest.ChainId, + 8, + "BTC", + "tBTC", + coin.CoinType_Gas, + 100000, + ), + fungibletypes.NewMsgDeployFungibleCoinZRC20( + deployerAddr, + "", + chains.SolanaLocalnet.ChainId, + 9, + "Solana", + "SOL", + coin.CoinType_Gas, + 100000, + ), + fungibletypes.NewMsgDeployFungibleCoinZRC20( + deployerAddr, + "", + chains.TONLocalnet.ChainId, + 9, + "TON", + "TON", + coin.CoinType_Gas, + 100_000, + ), + fungibletypes.NewMsgDeployFungibleCoinZRC20( + deployerAddr, + erc20Addr, + chains.GoerliLocalnet.ChainId, + 6, + "USDT", + "USDT", + coin.CoinType_ERC20, + 100000, + ), + } + + // apply skipChain filter and convert to sdk.Msg + deployMsgsIface := lo.FilterMap( + deployMsgs, + func(msg *fungibletypes.MsgDeployFungibleCoinZRC20, _ int) (sdktypes.Msg, bool) { + if skipChain(msg.ForeignChainId) { + return nil, false + } + return msg, true + }, + ) - // deploy eth zrc20 - _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20( - deployerAddr, - "", - chains.GoerliLocalnet.ChainId, - 18, - "ETH", - "gETH", - coin.CoinType_Gas, - 100000, - )) + res, err := zts.BroadcastTx(deployerAccount, deployMsgsIface...) if err != nil { - return "", fmt.Errorf("failed to deploy eth zrc20: %s", err.Error()) + return "", fmt.Errorf("deploy zrc20s: %w", err) } - // deploy btc zrc20 - _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20( - deployerAddr, - "", - chains.BitcoinRegtest.ChainId, - 8, - "BTC", - "tBTC", - coin.CoinType_Gas, - 100000, - )) - if err != nil { - return "", fmt.Errorf("failed to deploy btc zrc20: %s", err.Error()) - } + deployedEvents := lo.FilterMap(res.Events, func(ev abci.Event, _ int) (*fungibletypes.EventZRC20Deployed, bool) { + pEvent, err := sdktypes.ParseTypedEvent(ev) + if err != nil { + return nil, false + } + deployedEvent, ok := pEvent.(*fungibletypes.EventZRC20Deployed) + return deployedEvent, ok + }) - // deploy sol zrc20 - _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20( - deployerAddr, - "", - chains.SolanaLocalnet.ChainId, - 9, - "Solana", - "SOL", - coin.CoinType_Gas, - 100000, - )) - if err != nil { - return "", fmt.Errorf("failed to deploy sol zrc20: %s", err.Error()) - } + zrc20Addrs := lo.Map(deployedEvents, func(ev *fungibletypes.EventZRC20Deployed, _ int) string { + return ev.Contract + }) - // deploy ton zrc20 - _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20( - deployerAddr, - "", - chains.TONLocalnet.ChainId, - 9, - "TON", - "TON", - coin.CoinType_Gas, - 100_000, - )) + err = zts.InitializeLiquidityCaps(zrc20Addrs...) if err != nil { - return "", fmt.Errorf("failed to deploy ton zrc20: %w", err) + return "", fmt.Errorf("initialize liquidity cap: %w", err) } - // deploy erc20 zrc20 - erc20zrc20Addr, err := deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20( - deployerAddr, - erc20Addr, - chains.GoerliLocalnet.ChainId, - 6, - "USDT", - "USDT", - coin.CoinType_ERC20, - 100000, - )) - if err != nil { - return "", fmt.Errorf("failed to deploy erc20 zrc20: %s", err.Error()) + // find erc20 zrc20 + erc20zrc20, ok := lo.Find(deployedEvents, func(ev *fungibletypes.EventZRC20Deployed) bool { + return ev.ChainId == chains.GoerliLocalnet.ChainId && ev.CoinType == coin.CoinType_ERC20 + }) + if !ok { + return "", fmt.Errorf("unable to find erc20 zrc20") } - return erc20zrc20Addr, nil + return erc20zrc20.Contract, nil } // FundEmissionsPool funds the emissions pool with the given amount @@ -588,31 +595,20 @@ func (zts *ZetaTxServer) SetAuthorityClient(authorityClient authoritytypes.Query zts.authorityClient = authorityClient } -// InitializeLiquidityCap initializes the liquidity cap for the given coin with a large value -func (zts ZetaTxServer) InitializeLiquidityCap(zrc20 string) error { +// InitializeLiquidityCaps initializes the liquidity cap for the given coin with a large value +func (zts ZetaTxServer) InitializeLiquidityCaps(zrc20s ...string) error { liquidityCap := sdktypes.NewUint(1e18).MulUint64(1e12) - msg := fungibletypes.NewMsgUpdateZRC20LiquidityCap( - zts.MustGetAccountAddressFromName(utils.OperationalPolicyName), - zrc20, - liquidityCap, - ) - _, err := zts.BroadcastTx(utils.OperationalPolicyName, msg) - return err -} - -// fetchZRC20FromDeployResponse fetches the zrc20 address from the response -func fetchZRC20FromDeployResponse(res *sdktypes.TxResponse) (string, error) { - // fetch the erc20 zrc20 contract address and remove the quotes - zrc20Addr, err := FetchAttributeFromTxResponse(res, "Contract") - if err != nil { - return "", fmt.Errorf("failed to fetch zrc20 contract address: %s, %s", err.Error(), res.String()) - } - if !ethcommon.IsHexAddress(zrc20Addr) { - return "", fmt.Errorf("invalid address in event: %s", zrc20Addr) + msgs := make([]sdktypes.Msg, len(zrc20s)) + for i, zrc20 := range zrc20s { + msgs[i] = fungibletypes.NewMsgUpdateZRC20LiquidityCap( + zts.MustGetAccountAddressFromName(utils.OperationalPolicyName), + zrc20, + liquidityCap, + ) } - - return zrc20Addr, nil + _, err := zts.BroadcastTx(utils.OperationalPolicyName, msgs...) + return err } // fetchMessagePermissions fetches the message permissions for a given message From a35a571c8c8ef53a0d3572c7bfbc521059dc9f3a Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Fri, 1 Nov 2024 11:58:23 -0700 Subject: [PATCH 23/34] refactor(e2e): use strict event typing (#3079) * refactor(e2e): use strict event typing * use stricter interface on OfType functions --- e2e/e2etests/test_migrate_chain_support.go | 10 +- .../test_migrate_erc20_custody_funds.go | 9 +- e2e/e2etests/test_pause_erc20_custody.go | 22 +-- e2e/e2etests/test_whitelist_erc20.go | 10 +- e2e/runner/v2_migration.go | 12 +- e2e/txserver/zeta_tx_server.go | 128 +++++------------- 6 files changed, 67 insertions(+), 124 deletions(-) diff --git a/e2e/e2etests/test_migrate_chain_support.go b/e2e/e2etests/test_migrate_chain_support.go index 9916c076c2..5c6a53ec13 100644 --- a/e2e/e2etests/test_migrate_chain_support.go +++ b/e2e/e2etests/test_migrate_chain_support.go @@ -167,12 +167,10 @@ func TestMigrateChainSupport(r *runner.E2ERunner, _ []string) { )) require.NoError(r, err) - // retrieve zrc20 and cctx from event - whitelistCCTXIndex, err := txserver.FetchAttributeFromTxResponse(res, "whitelist_cctx_index") - require.NoError(r, err) - - erc20zrc20Addr, err := txserver.FetchAttributeFromTxResponse(res, "zrc20_address") - require.NoError(r, err) + event, ok := txserver.EventOfType[*crosschaintypes.EventERC20Whitelist](res.Events) + require.True(r, ok, "no EventERC20Whitelist in %s", res.TxHash) + erc20zrc20Addr := event.Zrc20Address + whitelistCCTXIndex := event.WhitelistCctxIndex // wait for the whitelist cctx to be mined newRunner.WaitForMinedCCTXFromIndex(whitelistCCTXIndex) diff --git a/e2e/e2etests/test_migrate_erc20_custody_funds.go b/e2e/e2etests/test_migrate_erc20_custody_funds.go index 4ddb0da4e7..8ff5be6327 100644 --- a/e2e/e2etests/test_migrate_erc20_custody_funds.go +++ b/e2e/e2etests/test_migrate_erc20_custody_funds.go @@ -35,18 +35,17 @@ func TestMigrateERC20CustodyFunds(r *runner.E2ERunner, _ []string) { 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) + event, ok := txserver.EventOfType[*crosschaintypes.EventERC20CustodyFundsMigration](res.Events) + require.True(r, ok, "no EventERC20CustodyFundsMigration in %s", res.TxHash) - cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: event.CctxIndex}) require.NoError(r, err) cctx := cctxRes.CrossChainTx r.Logger.CCTX(*cctx, "migration") // wait for the cctx to be mined - r.WaitForMinedCCTXFromIndex(cctxIndex) + r.WaitForMinedCCTXFromIndex(event.CctxIndex) // check ERC20 balance on new address newAddrBalance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, newAddr) diff --git a/e2e/e2etests/test_pause_erc20_custody.go b/e2e/e2etests/test_pause_erc20_custody.go index a1b0319c76..0c999d91d6 100644 --- a/e2e/e2etests/test_pause_erc20_custody.go +++ b/e2e/e2etests/test_pause_erc20_custody.go @@ -32,18 +32,19 @@ func TestPauseERC20Custody(r *runner.E2ERunner, _ []string) { 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) + event, ok := txserver.EventOfType[*crosschaintypes.EventERC20CustodyPausing](res.Events) + require.True(r, ok, "no EventERC20CustodyPausing in %s", res.TxHash) + + require.True(r, event.Pause, "should be paused") - cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: event.CctxIndex}) require.NoError(r, err) cctx := cctxRes.CrossChainTx r.Logger.CCTX(*cctx, "pausing") // wait for the cctx to be mined - r.WaitForMinedCCTXFromIndex(cctxIndex) + r.WaitForMinedCCTXFromIndex(event.CctxIndex) // check ERC20 custody contract is paused paused, err = r.ERC20Custody.Paused(&bind.CallOpts{}) @@ -61,18 +62,19 @@ func TestPauseERC20Custody(r *runner.E2ERunner, _ []string) { 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) + event, ok = txserver.EventOfType[*crosschaintypes.EventERC20CustodyPausing](res.Events) + require.True(r, ok, "no EventERC20CustodyPausing in %s", res.TxHash) + + require.False(r, event.Pause, "should be unpaused") - cctxRes, err = r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + cctxRes, err = r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: event.CctxIndex}) require.NoError(r, err) cctx = cctxRes.CrossChainTx r.Logger.CCTX(*cctx, "unpausing") // wait for the cctx to be mined - r.WaitForMinedCCTXFromIndex(cctxIndex) + r.WaitForMinedCCTXFromIndex(event.CctxIndex) // check ERC20 custody contract is unpaused paused, err = r.ERC20Custody.Paused(&bind.CallOpts{}) diff --git a/e2e/e2etests/test_whitelist_erc20.go b/e2e/e2etests/test_whitelist_erc20.go index efd24ef16e..1823947b98 100644 --- a/e2e/e2etests/test_whitelist_erc20.go +++ b/e2e/e2etests/test_whitelist_erc20.go @@ -43,12 +43,10 @@ func TestWhitelistERC20(r *runner.E2ERunner, _ []string) { )) require.NoError(r, err) - // retrieve zrc20 and cctx from event - whitelistCCTXIndex, err := txserver.FetchAttributeFromTxResponse(res, "whitelist_cctx_index") - require.NoError(r, err) - - erc20zrc20Addr, err := txserver.FetchAttributeFromTxResponse(res, "zrc20_address") - require.NoError(r, err) + event, ok := txserver.EventOfType[*crosschaintypes.EventERC20Whitelist](res.Events) + require.True(r, ok, "no EventERC20Whitelist in %s", res.TxHash) + erc20zrc20Addr := event.Zrc20Address + whitelistCCTXIndex := event.WhitelistCctxIndex err = r.ZetaTxServer.InitializeLiquidityCaps(erc20zrc20Addr) require.NoError(r, err) diff --git a/e2e/runner/v2_migration.go b/e2e/runner/v2_migration.go index 1419701887..b150263c6a 100644 --- a/e2e/runner/v2_migration.go +++ b/e2e/runner/v2_migration.go @@ -149,9 +149,9 @@ func (r *E2ERunner) migrateERC20CustodyFunds() { res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgPausing) require.NoError(r, err) - // fetch cctx index from tx response - cctxIndex, err := txserver.FetchAttributeFromTxResponse(res, "cctx_index") - require.NoError(r, err) + migrationEvent, ok := txserver.EventOfType[*crosschaintypes.EventERC20CustodyFundsMigration](res.Events) + require.True(r, ok, "no EventERC20CustodyFundsMigration in %s", res.TxHash) + cctxIndex := migrationEvent.CctxIndex cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) require.NoError(r, err) @@ -188,9 +188,9 @@ func (r *E2ERunner) migrateERC20CustodyFunds() { res, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgMigration) require.NoError(r, err) - // fetch cctx index from tx response - cctxIndex, err = txserver.FetchAttributeFromTxResponse(res, "cctx_index") - require.NoError(r, err) + migrationEvent, ok = txserver.EventOfType[*crosschaintypes.EventERC20CustodyFundsMigration](res.Events) + require.True(r, ok, "no EventERC20CustodyFundsMigration in %s", res.TxHash) + cctxIndex = migrationEvent.CctxIndex cctxRes, err = r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) require.NoError(r, err) diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index c86f291432..9b6e2d0b65 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -3,7 +3,6 @@ package txserver import ( "context" "encoding/hex" - "encoding/json" "errors" "fmt" "math/big" @@ -33,6 +32,7 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/cosmos/gogoproto/proto" "github.com/samber/lo" "github.com/zeta-chain/ethermint/crypto/hd" etherminttypes "github.com/zeta-chain/ethermint/types" @@ -358,59 +358,25 @@ func (zts ZetaTxServer) DeploySystemContracts( return SystemContractAddresses{}, fmt.Errorf("failed to deploy system contracts: %s", err.Error()) } - systemContractAddress, err := FetchAttributeFromTxResponse(res, "system_contract") - if err != nil { - return SystemContractAddresses{}, fmt.Errorf( - "failed to fetch system contract address: %s; rawlog %s", - err.Error(), - res.RawLog, - ) + deployedEvent, ok := EventOfType[*fungibletypes.EventSystemContractsDeployed](res.Events) + if !ok { + return SystemContractAddresses{}, fmt.Errorf("no EventSystemContractsDeployed in %s", res.TxHash) } // get system contract _, err = zts.BroadcastTx( accountAdmin, - fungibletypes.NewMsgUpdateSystemContract(addrAdmin.String(), systemContractAddress), + fungibletypes.NewMsgUpdateSystemContract(addrAdmin.String(), deployedEvent.SystemContract), ) if err != nil { return SystemContractAddresses{}, fmt.Errorf("failed to set system contract: %s", err.Error()) } - // get uniswap contract addresses - uniswapV2FactoryAddr, err := FetchAttributeFromTxResponse(res, "uniswap_v2_factory") - if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to fetch uniswap v2 factory address: %s", err.Error()) - } - uniswapV2RouterAddr, err := FetchAttributeFromTxResponse(res, "uniswap_v2_router") - if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to fetch uniswap v2 router address: %s", err.Error()) - } - - // get zevm connector address - zevmConnectorAddr, err := FetchAttributeFromTxResponse(res, "connector_zevm") - if err != nil { - return SystemContractAddresses{}, fmt.Errorf( - "failed to fetch zevm connector address: %s, txResponse: %s", - err.Error(), - res.String(), - ) - } - - // get wzeta address - wzetaAddr, err := FetchAttributeFromTxResponse(res, "wzeta") - if err != nil { - return SystemContractAddresses{}, fmt.Errorf( - "failed to fetch wzeta address: %s, txResponse: %s", - err.Error(), - res.String(), - ) - } - return SystemContractAddresses{ - UniswapV2FactoryAddr: uniswapV2FactoryAddr, - UniswapV2RouterAddr: uniswapV2RouterAddr, - ZEVMConnectorAddr: zevmConnectorAddr, - WZETAAddr: wzetaAddr, + UniswapV2FactoryAddr: deployedEvent.UniswapV2Factory, + UniswapV2RouterAddr: deployedEvent.UniswapV2Router, + ZEVMConnectorAddr: deployedEvent.ConnectorZevm, + WZETAAddr: deployedEvent.Wzeta, }, nil } @@ -521,14 +487,10 @@ func (zts ZetaTxServer) DeployZRC20s( return "", fmt.Errorf("deploy zrc20s: %w", err) } - deployedEvents := lo.FilterMap(res.Events, func(ev abci.Event, _ int) (*fungibletypes.EventZRC20Deployed, bool) { - pEvent, err := sdktypes.ParseTypedEvent(ev) - if err != nil { - return nil, false - } - deployedEvent, ok := pEvent.(*fungibletypes.EventZRC20Deployed) - return deployedEvent, ok - }) + deployedEvents, ok := EventsOfType[*fungibletypes.EventZRC20Deployed](res.Events) + if !ok { + return "", fmt.Errorf("no EventZRC20Deployed in %s", res.TxHash) + } zrc20Addrs := lo.Map(deployedEvents, func(ev *fungibletypes.EventZRC20Deployed, _ int) string { return ev.Contract @@ -699,48 +661,32 @@ func newFactory(clientCtx client.Context) tx.Factory { WithFees("100000000000000000azeta") } -type messageLog struct { - Events []event `json:"events"` -} - -type event struct { - Type string `json:"type"` - Attributes []attribute `json:"attributes"` -} - -type attribute struct { - Key string `json:"key"` - Value string `json:"value"` -} - -// FetchAttributeFromTxResponse fetches the attribute from the tx response -func FetchAttributeFromTxResponse(res *sdktypes.TxResponse, key string) (string, error) { - var logs []messageLog - err := json.Unmarshal([]byte(res.RawLog), &logs) - if err != nil { - return "", fmt.Errorf("failed to unmarshal logs: %s, logs content: %s", err.Error(), res.RawLog) +// EventsOfType gets events of a specified type +func EventsOfType[T proto.Message](events []abci.Event) ([]T, bool) { + var filteredEvents []T + for _, ev := range events { + pEvent, err := sdktypes.ParseTypedEvent(ev) + if err != nil { + continue + } + if typedEvent, ok := pEvent.(T); ok { + filteredEvents = append(filteredEvents, typedEvent) + } } + return filteredEvents, len(filteredEvents) > 0 +} - var attributes []string - for _, log := range logs { - for _, event := range log.Events { - for _, attr := range event.Attributes { - attributes = append(attributes, attr.Key) - if strings.EqualFold(attr.Key, key) { - address := attr.Value - - if len(address) < 2 { - return "", fmt.Errorf("invalid address: %s", address) - } - - // trim the quotes - address = address[1 : len(address)-1] - - return address, nil - } - } +// EventOfType gets one event of a specific type +func EventOfType[T proto.Message](events []abci.Event) (T, bool) { + var event T + for _, ev := range events { + pEvent, err := sdktypes.ParseTypedEvent(ev) + if err != nil { + continue + } + if typedEvent, ok := pEvent.(T); ok { + return typedEvent, true } } - - return "", fmt.Errorf("attribute %s not found, attributes: %+v", key, attributes) + return event, false } From b31794131620b6e0a96fbca07c2b11e6666b22ba Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 4 Nov 2024 15:46:20 +0000 Subject: [PATCH 24/34] feat: add Whitelist message ability to whitelist SPL tokens on Solana (#2984) * whitelist spl mint wip * whitelist test wip * test fixes * fmt * small refactoring * add protocol contracts solana pkg * bump go idl pkg * revert back to development gateway keypair * fix e2e test * add tss signature to whitelist spl mint * CI fixes * changelog * cleanup parse code instruction for whitelist * cleanup * cleanup comments * add whitelist candidate to msg hash * PR comments * fix after merge * move back to tests solana and add todo * PR comments --- changelog.md | 4 + cmd/zetae2e/local/local.go | 3 + cmd/zetae2e/local/solana.go | 1 + contrib/localnet/solana/gateway.so | Bin 278648 -> 366416 bytes e2e/e2etests/e2etests.go | 7 + e2e/e2etests/test_solana_whitelist_spl.go | 68 ++ e2e/runner/setup_solana.go | 5 +- e2e/runner/solana.go | 46 +- go.mod | 1 + go.sum | 4 + pkg/chains/chain.go | 4 + pkg/contracts/solana/gateway.go | 39 +- pkg/contracts/solana/gateway.json | 681 +++++++++++++++++- pkg/contracts/solana/gateway_message.go | 102 +++ pkg/contracts/solana/gateway_message_test.go | 20 +- pkg/contracts/solana/instruction.go | 59 +- proto/zetachain/zetacore/crosschain/tx.proto | 1 + .../zetachain/zetacore/crosschain/tx_pb.d.ts | 2 + .../keeper/msg_server_whitelist_erc20.go | 46 +- .../keeper/msg_server_whitelist_erc20_test.go | 202 ++++-- x/crosschain/types/message_whitelist_erc20.go | 6 +- .../types/message_whitelist_erc20_test.go | 4 +- x/crosschain/types/tx.pb.go | 1 + zetaclient/chains/solana/observer/inbound.go | 2 +- zetaclient/chains/solana/observer/outbound.go | 4 + .../chains/solana/observer/outbound_test.go | 63 ++ zetaclient/chains/solana/signer/signer.go | 135 +++- zetaclient/chains/solana/signer/whitelist.go | 125 ++++ zetaclient/chains/solana/signer/withdraw.go | 10 +- ...axBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json | 10 +- ...vd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY.json | 76 ++ zetaclient/testutils/constant.go | 4 +- 32 files changed, 1573 insertions(+), 162 deletions(-) create mode 100644 e2e/e2etests/test_solana_whitelist_spl.go create mode 100644 zetaclient/chains/solana/signer/whitelist.go create mode 100644 zetaclient/testdata/solana/chain_901_outbound_tx_result_phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY.json diff --git a/changelog.md b/changelog.md index ce8df87a44..e29e6d26af 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +* [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana + ## v21.0.0 ### Features diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 42ca736de0..e1d579cb0a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -409,6 +409,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestSolanaDepositAndCallRefundName, e2etests.TestSolanaDepositRestrictedName, e2etests.TestSolanaWithdrawRestrictedName, + // TODO move under admin tests + // https://github.com/zeta-chain/node/issues/3085 + e2etests.TestSolanaWhitelistSPLName, } eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...)) } diff --git a/cmd/zetae2e/local/solana.go b/cmd/zetae2e/local/solana.go index 135615051a..3e58418cc4 100644 --- a/cmd/zetae2e/local/solana.go +++ b/cmd/zetae2e/local/solana.go @@ -26,6 +26,7 @@ func solanaTestRoutine( deployerRunner, conf.AdditionalAccounts.UserSolana, runner.NewLogger(verbose, color.FgCyan, "solana"), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), ) if err != nil { return err diff --git a/contrib/localnet/solana/gateway.so b/contrib/localnet/solana/gateway.so index 185b938c3ca936bfc13c831b38af4b826e8bef8a..0fe82f24f22d1d765fb38561a8c2c37fc75b629b 100755 GIT binary patch literal 366416 zcmeFa3!K$gbua##A%_8?G64=uj)@E*BxH12lE#ShZ|cODNXEyI#}SHRm`n)6pvnAl zV&KQG)geYRQXeE5L+Ndub7mL@>%DSYZ$qoSgRQrv_iClB4}4?wR@7**zG!{$U*Gjz z>v#5?nSl@!g1zn!%sy*B)?Rz6ET=V(6nqeh}Z^6Bz_@Shl8>o4zQI`_kT$T6^(^wj$8 zG4r|a`RXMYIU7W2Mq@*zg`SfB%GZ z?UHV+BFM!x2*39WjQVeUB#QFUS`8l?QZH%{q9po6lhF6APf^AYgLVRonj~mtKv$EH z*cZUS`|RIidjiid6F*+6{6OnPQ7-zD@-Zp1z0$%?gRLD4I}Bz!r-cE9tMwgJIPD!Z ze_s|Kw!63YWbHdB{7VZ{v~Nv&CgtUdAN>EdarO9^h9_l~pBA5tm} z>vEMAyuYv?K_6;AZaJpy0k~N(q%B7kZUP+W#kA#!hKF=(|86;~;c4NZ`TLn}h;%_8 zqd$y6h{cWFL?8WP?+(btVulClCHXI;zq_|jroX8l&Sri*p!}Gn{HW@0wpUeudpqt> zJ||_@djo_`Z{(%BNfV0RF#96wZ`6B9`SkE)a6wQ7c(>Xn@JEWrGyj@obh>&6(4X02~AAua+t;tm{ zUoUvO%9QK8*utPR7mhJhv+9nul5MaOBXR5bb#+}@1^&WKe^~aO_1&) zKBNOHy9?H@%FGXae*kn=3>IXd8+`b(=z^qtfb>)js-H7Mx2y7Xh7TFN!|d0|?&BJs zt}(xZ;b|Mwb+<6zWON*wf{srp9fypL!^B@>=Vo>(hpoulH!bXEzAdcZ*f#p8-b`kTXx6waUJ0}&^ z$L;j=Zg21BD1x+?c%Mjn51u9M{jN#cI~lTiFJ^sHHbZrsdK~@weQ0m%*9)y*x6U|q zzx|}-`(^648_tq;eAOiFSUW$>`fZIqPPRZhN`JBbny(YZB+mP1E^1IX*+zLN*8PhAuV%>izjP!8HeZ*I+|FMP_>%>qH7wY|2OFQSI+cn(Fgtpyp+49tdJOi>n9b8a+9%}?aokKQJB?1#lNL;`8QpHzc)iks>0jf6>od$Z zL%Mw4gYZv1cZafd-(A1`_SEz}!tZ#|N8beEzxSTsD5nML3537@x8{Bf;s5%x=?7Aq zUx)P>fLPicH@ zyERF3g>A8-ZehM|8xKSJx73Wg;G2&B8-)Jy7M54( zS3jq#TO!+K(gpjWN794-kD0u8L*C0hmak8J%uls`S6Th07{A@#NDJ#U-&j|*9Q2sb zPB|#$rfsV;{8vgm=>B7o{*C3IY51o3(g@<>@(G1Y^SR(xdN1k7=C|`Hk4c&MGX9Iq zz_~s^eWlnFDZ8)8-BHqc7U-|PegpK^t>fr@BKm8ewWsT^%g~-C$4Q0#Alc^n>#){8 z$@CZH&-;H>pD9_g&PREisK1UVo{9R4{GF)3j%DdXy^t1;Yj`N1gHoSRf5F~`oD8Xa z50d`MsQS6+YU3aK-_xnTKxa6A__(xhm+6bD{vw@8`5^0I>$^+p=j#XwJ&F|2pT}oc z*GEzR1@EfPTe~W@?lok1hOHfUvwhPw=65hWZDYFb7Ut`=b`ANy1N2we?$C?Q_nVdP zJB;r;jqlGQ-^;}B`Y^S12cOr3_OYNpkuD;BpLd4k_e*@Jmk!+pz0_~z4yb=N+aot< zf9@t%+9b{xh*ZAJBXg z`!nTcVt;14BYb>+rrg!GYuKL$rGAj-%T%t$w{K2YGoinq{{9R)!~T4e_Gj|1+MnmK zy+@7jwr=8bU$sY$Qht4%tW5d9_@w0o{qBhIXU8h&4=-;q%W?ds*V9Xck>Y&(b34q} z7jn^aHNLAdOt}~ur9W)+?xy^tYs~Lpc-qEv-7U=5ZE_Q~YajR%(CzK|i9675`&dq8 zi~5t#H`(o3G9 zn7ZALb34N3|LOb-G(IU4Upk-j3h!4F?Tp8?TwmA7_6whedD5TV^VHZG7pPt$-~Th& z8Shp3Wxq>)3G9q9Y2RPO&S-2LZ)ePU=hI_nT(5lJVf}xn`nl+-w=@3wPUw$*D|bNs z$8Bd^Lj{yvLI1)zGH{E7UK7p#qNThxof_0oS|FY(c%8j)_b`q{Xe*m@lHUqI)hvQ7X$ zKMj8g!%rT-Um0KQ+(Gf2dQFG%ThzCX8{E!gvce=D+K004x#>l>CmG~%P%*utG{&S(<=j*MDIZw{ly-f4vqU+V~s(g|3 z4jJ9UR*&7sG(25neh0(THm2)tVZLtLrw{4hS;IH*5p;h-==OalUq8!3mM|YAcd~rVmhaP= zZ@xf6f4ofnCqnnf70)v@e;l)&N|Vfgr>Z|h0QLB5#dD703GENWSJyd6@8g#ncFcU8 z;jQEPOH$v!axPLYdWU$z>T&!U0G}oBBetG&zUoug;{$B(H#5KMn-q;DE?@8KYv(EK zc3fspEY{%c+)iqGwm1#FK-$Ch7yMjLYWmgfZ^(s^m(4qi^_I`(-PL%?V=gi}eEr1j zW%v_^xCAhAf_N66izCcdg|+$MRii`GoC^Cp<=Vm)Z5{dzsJe-3fN( zHH<&w@E*-%|RCjXRrd+}ZkhmFLo~&(WX7@nNcUTwniBTlQ)CP|qBZ1`o#_ ztmC17KU}KrUlr@}9CvIU63R8|mo8E=Mc*D^KePQtKQCDA7fkom^_ylb;HlVW9<1RL z_y#^jB40_x>h1I5(LwUT`)?o@lI=uhcbuaCJyHJoJUo3X`F-~GR~mL9 z{5InIPctvxHLCp~v`=d1AwDnOCbn5u+2_SgPuaY!Drf^dUOnRLC9R8p zL-Xb9IFZOjtqPyX`SCY}{)zMB-(-Ev?!~+WGlAOnz%l&GdHVSMy4=5Z2jF z$F4o?dF9ivUojY?e~WS!=W+=9XZn|Z^i_m?c76HXkLO&MuS@to>U7A@P>uX#=OS!e z&Gu1ke6?}G*AJZ@KQE8{&yX%ZKY{Si{hzPu;@jVUJNba08$)+*{elO> zb6{<6|KVE@{>Bs0USBs1`~Mbc=x`s^^>M%IlPzY~^s7JFxvr3&$1BWjvC+XJi5Yu89K40xBlg{e> zDAJ!6RuEsd-`cM5$lXJ)qlin&%%5iGxrpEA(Mfrw;_sf% zajDzZ!Mq<%+;{y3>EU!IdOG)A8>Y}>;W^O9qsJh}Xjj)`uSdK7Cp@3}joTm(Hm+B8 zXgf}($Ih1Xsg0_~*uN+0v4cu~wk}sQ|M&6abk3*#?4@XTmiNneK6Sgc?-ugqG|s2a zRz22Z`TEp{K9MdFmCnHV)K#Ld{$D(wdRHIpurBK9ZR$l&Wk3c3eT~-y#+ku&$AuX^22c&>q?;= z=Ii#ID&N#w)qT$wDcxtoc{b1)+F@IyeY?#5sp>B(s$|soZsYt!JM6gfCAIsZA%FT% zPDQJ&cGxk2G46lt2IzP8UzCUbn~Qi43g^}U`?)ngKkE15x+)){9QeBau+g`h@-o%F ze|HP>b=!S_kk0-ZI=vl#*abTKS)R=^C$qQKN&e8@+6uY0`}9erhy0o9+`OHO%j#?C zrnYOy?+N>UPs9A|F**13ROp49{*&s3kE=ZW^5}&hO8cHkz3`m3H}W&H+{T}er7LF&sOiVth0Ex7gih0@yO@180W{Y+i+Y$ zc(_jD?};EhTpvjb93K!Ku7~(LDd9NNjB#ez#zUFLt`r#K;SF7oi)O3WQuQaB?}YT6 z$#omh8QKeXt6o^CbXM&JU$-&7`+CEK`OXT>m$tO4AM!`b3G9V-yNI=Es7@@GPxKwx_8@ny~g|whWmZB?iS|j zws((0`WwNgfPQb^$JU{J8(ChZN&U&}h)D8>c0{cDBC>ol)c5&L_f=NEsq6~B4_7$; zb5!_b>oTxk(zauo4*0P}2D$umIsY5xdk^pg_4^{|mGl=|`ahNQ-R~t`az0-1Ro8Qf zKWmSBC6lpXac&Z}onSzwa?m`JOxl_j%sGGAwT~%kgtd zXYqZWo3&m0tX;QQyG~d7Lp=z666&$TJ&*^}ui1Rai64y9nH_( zK3(^DlEp^<<+Lx7ity7D-L*%zKOqqH$RO9tr9%P1>e|<8Hf9$4g9I+#apjnLEtwiM>}Cl7y7>Ojr1k^ z3FLE!pXp*k$y}?q-BU>BntkQxZO>ymf9C|{peF&}zH{X;ra6@Nw`uiVLfB94P@bu+zCok;QT+;C57 za)j{Wo$E0KM}6zNB^&(8QJ%$`b4?hE7u?eJVV zYvlbaF?mDytNJE}cP+kBsTrdep?{zSisw)kR|#Fs0{eZJoSl0Bz6;-hbtzT|@W}50 zru+c@CxM*~ms9PR;d_#j9ioEtWA|6nC#LrjJC_&gPv`@rkEEXY>F3H1;oH{Zs&8x4 zh4vWwJ>+K~c#jQu{_;MNaS?J*-XQ8)lAS0{=RKQRd`Lg<%>kXp-(Q4}Uw!*{J}#=^ z<10cwF4pQs-mb|UFhb-%Xy@ayJMMfB!)NtZ!^aGd@B2Xyq5f_sW#f(Qzq);e{w^$x zO17Q}xEcALtoQCJf=nFr{(fl(*F)g@buvyCO&j@sbk%+y)%JJ$+0PC6x}o#q$$a1R zP5o7U_shu7|2gaI;%717=P%NqHrMp0N4JmHOJ5V%<>418=f0Y9Zm;RL@0z6FLjQ&D zK|-z{y{}4-)Ae}$tpA&x5#H~B9($_K@h5ZIrMHUynY*06jU%Uf-YPMDbo%!&`mB6W z4&u&y{Rdg@c{}NwU6s}2at)5M`y|#*)p**6$s(r9&S}`XkiX~U?`K!zna}0qS8{Nc z$iZBbgEQU_4w?KNq<_ZCSMx#i!11T4{ADm-#12|Y|7q)oO{~Ao1JArZT4Ck4(?8>K zu*J%^{&>dAA7%OH4H_NlRqw@~F@4N(^YvtauLC6KHEKk@u0{Q^W~PH34d?SI*YkXp zK;}7hOErG%`RYYuoQBJN(wQ27brCq9=Z_h_{4_>5oLtnW{lmu}PhXtIbMPA%_iB9W z&BU85UZ-$=8sByC_X;*H>Cy0w%b4ERYg$)ou++omW4?|fWFThjW+hYZn|@90^$ik^ z`NZs-81CyA8J$v3*fIHY>>YlmKMUtQ(_BRQ^M2pi(+P6qFHYmTdC5gqA6w_hMGW_G z6zd`H__Q%sNJ6_jSA5JHkJii^|1nq(8uNMT2QZX4raE z7xRsYEeJQ+JoRY5@GsWr>U{&h7ymf&AF0U?5%G0Gr}JdxAFj!db~~N?hidYJ%cqn7 zU`>8V_UYt5P?KMp=(O_hugQ;fvC}DkUrl~#`qRokT9Y3=^mNJ}tjRCazthUUvnKz! zr*dt$qq?5mP!~nbl76%K zptoP();YBEoc`_>jz0MM#PK-h|&5bUse359x7wUn+DRko={s#z%n#rO-)3erh~? zf5qd$7p=eekB5IJqYM25e3X9?=sQ+J-#G#&J>&VeaGL~*yE=xHRr%=S>R3sGRsMjl znxEmbEq}Ofjr)gTzW2-e)ohyCQTkSv?{;D)&vu?;jLjUmeUcKCMc7<9@WK3w|96m%|Tystwiwl9>}ep$YLmdaJJ zp7cR}rD-F4Ckw82KOtXtv4*?8ak~`s@7W4@x?W}Z`b84&-=^Rn+bzIw@Q1tJf&|X*3{0SxLhIKYlV;SVOKvY<%7bI z&zpjH|DKw+Z{FWYx`z1kb!?C1T0tnTI1Rl`{Dv2+kM@AeMZc+jdN0$VTsU9v&qcQ= zTwCv~9Uwmanq+)CsAY&&_3I) z)`AjNFRGn0p&E)S| zt2et}b)Vhj)%QIffN+bu)xt4<87G%>pQG{UukMh=_c#5WHg8{lSJ3In+52v1e?7fj z{#n~)vicy!gng;`qJ2*T`sweIs-8+JM-(r@KO2O*Uh;Dz zv#(_Ryk4y@W+~2(RRSX9l>C1w^Nqcf_|9hjzw9aE|L+HU4*9<)2+!sp!{igzm-Kwj zBEo3r`JaIjqr>_T=3V&SWb{l^O7au!`mt?+J#YOfSxPO0_5+sIZ5(f~?gG0ixmW_l zbzMh4y^rZZ2hyi6W_UjLKK+sQ9#A==JTzwOS!1tay4EhXJJ+!qWnY}~Jf+@}`bGWB z3w@Z`2QWUx`TF3(WrST0GyU9bh6OQ9| z2kivem?m+r+a$ z7Fm#Dy__!-aXEo^MBul+m-)svGTqe=FVPzNF){{CGuf8S-cl?VPpv|1m+Du=YW(Vg+#%1b+_7yUa3{@pN? z|Dv~vzPIlrB*RSS<R+C^xGb3SGKi5Xs;*`V~qFD9JPDdLRwP2XJA1HkC} z=(&g;)6WljyQSO7pRpCh=lUSEcMrkO?+<{U!|adiEgic_l#CjGO>Ujf3wJV|(=*%T z+v&;AG!ynnkMa{JrQ z;dp;TeZTo1oF4#hRu0LH(t7gOnqTc;|AsZvgZ>M|NBbYBY5%Xuc{kukJNdW`z4oG- zaUw7I$;ukvNxg zpEu;~T&m9-e7s3-X8NL(3wpdh?k7vw9?43k1NBIU??}V<@4#l?m3I9n|DqQ4ON-3k#_*E8kCb*bYkXQqJn4<3%gynfnzeQ|dEw8i?;d4Ae; z+AjHNH|UE_m<~ar9*;fovd^AFBIU2QFYJkxfjyCnuFy2=r#)No=BHhw>q_})FVXQc zr2EG;be}IdbJ5+F|9m~SnVw@~AT`(e`U6uvyg8uP9KluAni5<_6yKC};4}PyAq;oPo;p+=w zy2V=+^i_Fdl()~`rZ=}0O)3HB>bp26HI*jl+ zy=63>u19Lhh3d^k3+3HlQWP5US8kK>%-91&P2>aU`$g$r^P>|dei8cbnS_4G2k4#ZU6Uu1 z|6lk8>3_{6^!q+Z$bYs6{-XapdHny0OsJ>k|EcBuC6m%m`|YWa_v?QAi_!aQXPDkU zIVruT?w?JQ(ChL&)jifHQ$8*{P5J1r(T9=J-_Lo4y#rFK4_*Ey=#xd1cfXH>b4jWX zX?{h1|0AI}rSpOCKKg}M)GGp?@8s-#&*W~(C*}|1?uB1r`#pD4{~+ z4=yRu?!`V%cz=EEwcpcp#d^+L<2kgS^Ys{wIDY|(>^py^XLHe)HD9<6u+R8;z{Z!) zbV<4Xev93AOk%UY?fW80Z2H^3vupc#{;pzT^TF`Dlp){3cYH$r`1{&e?+N+h>$?aK z`H|x$*DV`1)Vk!u6=M<$%&%E7$q@S~;G@dYK$2_WjDWSJ?NE zOn!ZxVC@yOL%e*K+hmyQ4QsFcftEW_UKeUP$oE9$wIBR^lI1m9U$E~yyIi^)o{rqr zO)7UErThePTPt_#CzU&s*C!x%uYL;Uu2!C)*WUifl6RqTUwQG{zV~?aF$60h=v0cK`!i-}YU&dd~Tb4qP z?)yVZJT%u1TmHk?7zWzL&Z+rcrKl+3E zApO<-)pbfgC)2%^^$h7aT+^OT$FHkgAO1NiCi{Wv*Up1-wmaqbJG<<(SpO!mDGc7y6kvz!$kQRD09_u!%g7vIWPF^JP zwL!{gmj9d0_jl#p?n>-@VA>KXfBk+|+QN5VvU;@e-IuH$g`?z;owvJi(Q_a^x=x87 za(+qS1Q}3m0jy#D5mSvhTQ({Ow&wfBS0UePk)~J=9G9!AAAvy<)~cNIf9$ zIsFWHa1I8T)9w2;K3i|@JwUv@2N}-hi1H1zTS9r*4|yv17NPG^x)B z@g}1zFWF1{ejdj6H^Te}-y(a$3G=7Bn111V3Cp`0tk;5N8t_Hwx9#$4$@B)U0gE255d>V^zl_Z_mlRvE8h63g2gX4_QiB&5+-xunn6$Ua z;Jn2*8jRI9lt05@F6^hhk->8;{j=+I!4kVVveaYA{w~f$xaH+?Pyy4;##L zYiaKxgR$#^^al;*JX-eO4W^u>z55NO0H(eB3^qN{J8Cc$cG^2=@N~n!(_mR>*79~3 z%=N^ycfeo~2#xP2yj&E7xcCL^ckxvUMoa3SsVl4#0bGl)A~UWQ6=Hb(5dT$f(j?>L$OFk$nc6ejZ`_l)7y`GjhP<*`BgrY%tq9 z898JySljUM%uN#O5N@nck?sb;;doQ~9(#4%8rAVw^o06I zhn`Tf@1X;37C5x;!~6O^9|-9f6gvF9WIt~sdP(tz_88u?K>cdzeTUS+_j7a63MpOq zXYURByNdpOu(V)yoPF=iA?--Gve8+{&-7FJvQPW98mI`vV{yG4Y%?R`eTLWb~R zMi&3K5+-7s!L1T6^B8SE)T1fjyUcquJndyWhx{3>u?Hb9Xs6G=O6#flWIafIvHt|K zgm+ngsu1tmIbUJAu4Y7we36c|bH1qI7_T+FYbpH->0UeMFyp0is9%2CPc%NH&*kX9 z1oZ)ZFnqul5Og-mQ7yeMQ8}VseKOxU`RC2m{&yzivhNgf`6X$PCqpi`Od*#Ks9g3_ z-YQ$v&qW_nx$$+zWXR<5@(tQQl0^_0k;@^I%gb-i`i+KPZ1Oy0a(N~DVKTB#@#k&+ zJegb$*2v|56@+KAT-MV2J!!A1e&hF2&V(Gj;S_T8$tOvUc1|Hjzol|CXmYgE?xJ%8zNj?!sr)?<#-O z;7_S9`_<~Fg)gWdmw&2$sqhW;y9{r4;cE&fUCUWsh50X>bFIQIclo*wg)t8NAdpXt z*RY&GzYKSij@Ji+^hkHJq<6e$qn@p$_mOe(d=vZs85plir;z8*K1uRCFoisSy$Jc+ zLHVc*sGofYysKU1A*paYUOR{D1}*Zn+T^g1G04>$mvw)3wPG6bA9&Ss{D3cr0Apd^-X$? zX?^|kwETQMv@p;2Fa_<%WHBK>zk&N4-<5vwsF}3cx$P@Z3!$6UsNSo&nE7GXe)moM zjh^rC@A|F#=S7!X#P3q& z>wlg0TK!AZJ_kSCfgh9c`REhm^Z0i{*)CJ_d6$wA@);vL`26;O&-wamwSDsSMV&7_ z9{Ruj1nK|!Y01fs3G`#;3Ht8|=zl!)e*X#5`~B0Rcl!i-u`&R9UwfMLe)9>^`_0p$ z_jf1ID{5QmU2&T9o*BLI2BkL_Z8?D-XHUEL8eIn-H(ifC!S;RZwAxoLw~7n2zE5@rWi4UjJ-4J+;Fs`A!l5T~5$#{3bBCYdJGdyP`wGsNkPi5f z7IcK=-~Nh?cOv{Ap(8B+=9MqMM#HsCc^W*?3n-_boTytaQKOo}Tp~ z-OwMo=p{fkCEeif-S^15%2$XWTDp6BZqatQ>*k)j{QFQRqj%#yE0x|ilt6E_-a&h= z4C#U%_H*7-;RRh7S3LH6-)W<@H3RhyEopGbO(HWTJyd0c<@4bmTo4fTh9@N?5As}JSADkoi~o>u^ih<(@i`mjHAZCJk|EC>DC-_OqO zXDkAS>V9Y!m&K#?vOau{gs-oAChgg}U(z*fdtLnor zU*oeVLqMF&8jtkj-~6Jfp^g@LeJ1~!+T79zbAnB8)coz+b1dV97DX4v%_@O zKBYfiN&3@S`weE_OlKW5m~xiRI$$ugpzLq10`=mrCjT%U$NlX1w>4PryMe#!XBKoN zYW>XL(02s#GvC8|S~ExW%Z2!!bhD)M^9#ipOH|_&8!oPEm+)e=xDMlUGRX3a(V{xk zDH$Yv#b{yO%M>R4#puGi?<>suVSRD6z;b^@2~F4dd5($anwlZ!yCtzW@b6w3Y7#rg zlvJ9Pzq#n3`s-(25gn0q>t}XEe)~7)k7uZI3ZVkr4{W)eG(o{PA@k)Qc+bc5gn z{i22h|NDVn=-(TC0Di-0^eOn!o=Ap~fd9KyPgLb-DEg+xJN@Noox)Z6cL?nJ#3z%Z z{u=r}rE)Z2{O>pZzg+#|jQ2$UL+}f}_GNbb%qq8^qQz@s|urYcE{HRCA zy{g>aVRGqo-mc$MtI~N}G+aaHy9CZfx60p>%Plq>LOobfJvhVYjEv4_8lA=H2ZFEI za7*+N__7PJMf@tAH;Tjpb~-z2Q(s1^(%BaM5csloU#|KkZ1;5%pVL#dC#Q2| z4V^hLZ4>`)VP&Q9llw)*8PC=A=3+xjbiLAfnd+}9otH-MQM#O;OQP2+?DDxdDl1&& z=S2eNqGtJfaykcV=p4{~G-~ZWXmtL)@pFFkPQh1f$gBP-%Is`R;3}PSteu_C#^}2m z?{q#(HC)Kg9*NJXc0M_s?KO11RE|6)U_HtIb8^qo12B*8TXzb&}rCRTt}icmhMMzl%|<`~Hbv6WGs}73;ot z;zosy4|V@~;{PaYe5m`*39N+6J}&94tBWw>%0=JOdW7^Il6u2V{$h^wwkf@bt=@;M z-n*=Q{`tgW!BcGb>Iv8##puY1x1pTOF8uO|n-uo;`QizT|5bVa{0WZpRXM_lC+A|d zoRjI}bv5ntnP;MYJx1p`qw_6F=h$U}RGhyfBk!pE3~<`U^J>YBZh!K8T5^+}t8L-= zw0!+SB+tHAhWCb~_!IG54VLqPOy}uE{9*n%Ftq{B-H`;?k}AehNR?b4cr-6FmgC ze%b}Pj*y@BLbbC{u8=C_K3n6l{;^1YwJrwr2!FNyz`8>1dqQoZ5XMAj!@1n9l5~P1 z@^iG+&=2px>$rtT)qya3Vqx79%ynO=Re6s8mwax(uaK5FXxUSmv=}w>i>;3 zl7I97>$!Ko`njl#Y_k7$q0kP0K-WF0^Xwza57ehQn77OMd=2+`u$<#pSk_0>59!z^ z^^y5|hG(_Stx)b{_Ygj*>Gx^-k74i-7v85hF6|kfJJa^&GO*V3`aVf-@Yn|CNBy7m zZzoRVBA2UnldJbr?yUW*^**5G`+bP8K3d)o@nT)!FzA7@5r=XXzX<(`{K?l{qx?;G zjgs#b@+WO+RDQTVOIw-@KB|7&(roZCgO?giKBg@*3^u;EL`+xUdC+vLrQ2@tcAh<~ z$I==;Dqn+op`NJEyG1aP(Pi5Ilf6sTFV1|P-slVQHBEvK>w%vO&O0W<e1&}ge8#@ApjLiYWMecLZ8Z937=29>=z|=h+@rFw zC-Yj9`$rUy_y7E~L)z1x3^_QrP3p6kauAQue$31JH7HoV|EuL^`e+aJPcHg(4M+Ji zC0*;HdsPmTYXlG{-+fg%dOY;8A5B~zIGOy8J;ZXrr$pp<43l_q)q32g^i=C{!MJ+l zq6MU18b|40{8~*9y(ZVlPb5$z;$&S&+X?y_>0}*G;m1?2lhF^lKYz8*o9TC0m4e^; zU%H)q9)mF~F4@EWk?duE3FWS_W*j*zF!cRW$%k?teiQJrAEsN`|Hd9rv~sSW{V>FL zaFYI!43Qq-s~7Q3Mh__;R%(ci8_f7?51^nIFmM)xT!cE|)ePt{&G{if7A)0ch9z~l_+$+1zkmwu-L{M<$u z?eTSKkB~3NagXOd#@F)M-{nKTX65s7?Wg(r4;i1M34A^(_@LM3)#$b9QUvfcs9rm! zc#^$GiErln6`!m_D4(B<{&N2R7xtHSt)TapF)V6`%lgUago4Izaryp?B8c@Yd1PLZ%Eq)4c2CrdEHKfb@&!|z+hpJxU{X`U@2JPEe0<%xX<8b zgL@2?3W!VFRvQfM26&~xoPWg&WHO5Itt&J9o^HT9UZQcrbLHK6#T^`a| zpV9uZLCQn?2BAaKncull0>vd|)*JZIe#vg~rIX?$@#I?;KSaKI`ALO*-yqEdIZ4WF z7o=A?S%gv~d~iVP3qEN%m7T0Vn^)r5{)k_O^ds#|FZ83!bh4SWzoDGX0wZ1L_3}sD z*gEw@{JcqX?(gNp407I%ycdX-u`jr)8o^Zc@DhO_!n1LFXwZ-`;$!X{c#EL zc6XS4Kl};COTCrO%EuM%d@<9-!zLdR4}Qjbc#a+KnQObf)Y1)-PiPmk!zz(K@G+A^ zG=jKz_#eiXH$;7%>D|%4W4sWf^bFI^?Yy7)vF)ktgT*FsF1KjsW`mKhbDjJVw@Tzo z)7!i{-u;inFZ|H@jQkT}$*=W!74yZr|M&QGd;Sk$ulFiTH%hrc|5kc;|Fx#;d<)|< zdAi47<4b3^<=^waGQ6aRa)Ej&J@+yltXHNxt{UyDDQj4BY&mw@!&`44^y5>+dn9+4!yUB ze#`slZ+%4l?ltdLztsDX`pO?k54q8Hlk(vuJ$~-h&?}@PU)QR5o;G{D zqQ&cYwnN6V&_4eysbg5Lov^3wWBOVob4JZPRnL2fT%&8l zZa(n4a<;L&m~vBiHRY^)DdnedtHPz;HR}63R^G4H`XK*r39hib!ln>%-KeAwQ<#|DPEDcPRhg zZ2bSQ@+B@mVEi9bxYYX>>euo=EaxEjK2+r=`0nx^&SPNN%KCxwcd~kWdtEQ}&P9Kq z?JRP~@w;sJ>*O4awd*yK&iU-$V;Q@i>9Y6jmlBrsQpJa5o{K+kN=Txx4t=5ckQ20D zvW@AJWT*Z}9wL0uXClVCWZ)k@n53*g7`{=KoWAB7LCC#>!^hG*}}aXlmC_i@a}gq-kP8PBqVUGz<8Jh}O4ibao#pshmf;?Y)(xGSlD{tVgBJVDI<;&U&@5|HEzo#$w$= zinz93pmT}X#(qB!bfTqv|28Q*-&PynoUiK)cE0y0jCwR)D)rc=#p(MlfWvZELtow} zg@{A>|1NwA;~$WCxrc7}*&e0B1H>Fxfg z&<&T`Jf=APIU0}r=#TD)^gyqn-+ohM9`WxJTg~5(NcPOo`pft0P)yWSnWu8l-7-kN z4LyT$(YBND@HvD#b`ajp`xj44byeoz5I|q)qfR~F8mQXj|;t^ z^XIPwoe|5~!*MJ}P`>YG`rtv!XM93E$MH*GxRlEi@?8yocm8u;FXex7PrIh`^?B#} zRQQt8a!oJuYt0{*XfUR2v?Joa%QZgUvW$4|T}r>ytbS_qsVY5xtMnxC3dWoM@b{KZ zNzZP|cWU#`D&CI}?;HA9{*CJC{wnx1RsGUg{p3p@6-+vd>r-*xZKQA3YSz22%iwhe zuQRyE@~<|y&)}5?Z&7$6y(6TzZ;Pf&K%}@+)j!?7ocMa%>9?(*FLDG|<;O1KC;BL{ z@5d#qmdc;KRxFW;w8dgZ$^w6|=0Ocpae>?@^L^or6e`QRQE7%trv5&!O;>SuP^ zIgWn|`C;Q~a(}bti{D0hjo)$^;ei(QOWXfM)0fu#clx`yzngZ9lFqh4`Yk)@_j2Bn z76uef%H&t6!2Kr~Zd#Q$_KAM={d%_-vUmC_+RWMy@D7Z; z3wI15KK_Mx4r3e(($|iA(BnSNK^`HJ(CdFMeXYyRtyk@Yg<7zm6To-Lp<%=Fd|bHd zUj=VjldE3Pa;*OX|0A!N%nlLbL0mHG{4zexA)e&C8Tv!+8z{fzUEB$L({h6P9=37n z)4vs#hto+Ix2&8!R?apJR(df`r%i9_8e6|%;$WbDSXc9su1h~j^ByB3z7_5 z{dQ9x(>3OIFg$Hzz2$qutk)#t{^1(>yxbG70RN5{eTV6n_o<(DSv$q#pU>;6cDwN_ zv%`&_Q}92rc|-Lb#3SUdz31rfBl~>R-%0jyd9rseRZaqVEKVwq6o6C7qt8>)Hsh0@e!4aLq_*O^>Y#J8`$Mjy_>KyxPJuwuMB&ehv8reFnV$9-MoD zz4Tn+2ljgh0=)WNo&ZKUhk|m(uA;p9c|1R71^h1y=YN9#LP?7J&=Xw`avUz!nY`F~ zhM$W!liBteda%)TGRDTkJQMZ|BCvelxgT z{j|`me%S9C1#dVHU3$CJ1#WtelmO|;f|w(cR!Et8tS+17UoaeEMJGEU&D0W_AYQ( zPrbiWA$^#qIX`beznt*i&q0=Zu@Ek9!n|sV`lq%Jlg*DZJNPTa_nu|McZ2bjW_NXd zFcsc4`&EwOCA4GHHJrc3OK9JwYdF7+m$1L3Yi$0zq*=>L*NkfZ90L_mR#xvH8k9Hecy@zoswkB0Z&9qYBHuIenj}6m}5q9Z+B9 z4=g{Mhmb#|!d8nXe@cbKVDhI_*kW*>Xgrb4a?s zyA2r5-{+S1kcqdlL(40*eBR)l26LSwsSFx?*y2Y?pMCdL?n@bb(Bcmme8Av-gZCS} z-{5@;*RHRoElV}r-xCk@^p|mdd5G=Xy6h@Vx6t%osBey7{!n3kLwz&468h$-mY>-B z+{OB~6X-hQ@6PNdFfnNuP9?{ymR;^?vQ&(aG%OON7DVviHOx*WM z&&q#+f|0+!?dQ@#&u41r$xBkFKix_>Og6K;t{&=vv9}SQzqjr4T>tK>_oJe`FCayn zmOax`!(WWcEKTgK1#i`#E_WM5D4Qi@v-xg^&0fSDeqW$A-CCQMuAM{q>YmSfCAV3= zGJULzVV(|t9F%_5Rc8Mz*7ciRWBu3OqhC_b7Y=#TXKj+Bayz>|R?o)#Q>W6Y# z;FyiKljUm)^2vM2%9DlG4r>$aW{EtQ+<&YKOl5NFCD3Q$M1oa($6K||C-5qe)tyAr*_{Q;qp)PE5`@CD-V}$ zA7ww^PJM@TP+3pfd%WApFL}RS%L(;^zsFl!kK;mj-tKdS^A@SL&Rf0}yrTfUhAxJA z%gW$8wV1c`1u)wEg@JrO)$d=*eO=Xa-6wh97bfv(zoWXH<3lJ9himliQGrq4FTYIW zYv_pSUFzR3-$CSij}Rfwzw?cJpZu!qe|9t6*J1q~2DvAx_);ioamkR4r?xNd`!|{X z8)7=&FNfZnYTh{-%rhsOcj96{=z|<2_l&a41*c z*U9MWIR#zceO;9<*Ykca4S2s@qvykM%HLT|7c?uQk}j#BxO9PyE50tYfZ?t$(gp0m z8J{oFY}xlTP;SE)q*ItfKfzX!^RH_I^=|jQt2IS(+cNb-`8XhaM*1ezzvO4J?taz3 zsomRgz9;tXv zae?~A$7{?-K}YN2eyv}o_wP2nA8YWW(t{oCjyuH{>3ip-Kt(c zuJi+LMml?U9r7~%o%N%dE~IZ%=<|1L(p}bGyU71gucLi{ueKgnPodW_|3>;Js@LE6 zx-CGg;Nxc~8d1wRM?=nP@Av-C^`fUnC^!E7=gHFjxuo-Z z>8k{fsL;oM|F@2FhNNB=)WszisW<8UU#x$FFLy{1@N0vZy4gOAt-Iy)RTI%u9hBF! z&FqJ*ic+C3y zW@+?hNmpw70}b!);JMzc-?zO>!`;3twY`(D&4Ut(Z+N((KM7=`0C$t-1 zP@`AE{S*1_nCg|hzS<@2?CS+x)F&~Nkhs#;F{R7v-5Lu6#QD36)qcWs^8Ff3pKd>( ze#nn8;m2hA?auivwl)xz0f}{lYucAVf)6TA5#4J8QwphuXJVaJ?!B)EBB2x zd@?fdeg^A}+~LBF5F0G^e9S#AHx_C?1uz42>A z$%wGe5BUk@Q~A`zc<|%89?`1-{H$+@JXyWZg#N|+FVw$}-X!hgcEJess?Rf0*;|5h zIZpPnzlMCP?T>v@f7stIk$kA{v@Ozq1P1?$7{J5WD z{EC#0WQ2BHar!9NYlHNSl0L+z^UYd4br1Ofya&GqJI(AnlZWd212*oInI3G!{QqQn z>Y*couWaL{-A^h`|CrK={ZZ&cdG}1~F*b#M8oQL``S^-{aZ8PUf=LAWrMf3vKP{=t zM;Hnw>%Skk{mj@Iu%kmcU!!tv{2d`*{5u{p-#mtzCX0kI;^M^|$hs~zyKD*Vv5n0d z9c`M$b$}Rwjm;G7_@W%c*X-2r?hc*{FSVUeIJNzmWavS{ZQoZP>!!lW zsIytKM;nCSSP}4hKi!3Y)pS+;&HEDRu4TlteX07P++8Z|GMWC~0r?zaIk5AW0yaCx z*L%^f@x{>JI}~p`%Kd<<{$~9Xdp|9wt8$WlFY6uBu>$xjR-R-V6gboqZ`=%h@PDStrvIm!2U#fq8SNCq8{6^gHJhe(xz4T_!aW`@!C;k$WPl zFXB0ze|W!*^Lb@Nc0XEdAGmlf^~nb5XRr|CIouHGyh5WT9=)FT_V6p7PlGzUCwP@c zNAW!BBi#E_Jaeg+vip_uWHKTyi*GC_+}W-k?hENWHeNtO5%)Y5&pc>x(K9A@tAtKy zD!<3LO8Tq9qC?^0JX%xA5j978VWkQz>QnWVjVEy)P>^l`7x=RKrK>br?%#6(Yl9G@ z^yKkZ%6|*v^&TPUarz`!=~>74c>XN?f%27}1=1PCW#yP1gLZ00xYQ5+X7jWAxKba* z`v!>=7teo&`oP-^c+N8k3!bI&tJC@i&^OEf3HLFjo#5g|{zzwylK+iTkhpZ#AYrme z($!h`PKM7H0+DXle!}NV!Q$e^=cu2~+QINf^dbpgAPoxtJdR_j&Ck$I%@V%Z{LUpv zCG<*h%iv44<>GhR`~mer3naD=-^pV2{y6xXwex!g5GVRy=>R_D>$Li$9q*M^lY6=n zsC?}Kg!HVpr%wjSmrfuTx2c8aB3!;U3w@xo)8(c79>weV<00NV_4~4Mg?b$A4SM1- z$B*nD^)U5))=w&&=lZ?q?(O}Gf77h{)sJ`c{adFe9%j9eA64zVMgEA3cXK??(v9*S zEaY0#t+sR{RzK@6@ovuVJzuBypFz%ty*=XHoR@b}e4=>p_sIuA3)aPZc^?4x{IxuG zE9v)dWjzMpr+#S`T0`1J_@nLe5Flxny_B<6f?vbeGkle_tHRq1cKvWI;dq$m-7|iU zkbizJKOQ6>vw95k-eITrk3DZ;J*1tr9)qM8>9rnxR?Z%tvoCG^XHCDU_XzV@KZZOs zOVZ8ecV5nX;48wt|8JIh1MW0^meDb^J;SGT?EXE%l3(d~zGM;?55A4z@>}I=Xh7jk zcGv8FeCN%YT;ziG4Cv7GgS2D(U6AgMuWA0Y<;V2R&V#sv@8H8y`5%|L-hz5A7ufB> zT7Bl@jMqP|JV5&9NG5S<3+<|^9l1c`vwc207bxrK+OPfGU}$eFP`hd3`dL5hn<19d zApFqz;cmj=J6@M87I_<`oWvua((qEL= zSNnaDbQkLz#!CZ_qq~-WMspBQ3u;@E-45YwXK$>tubDOUQS-6wzAuZY?O?#d|ABZ1cT&94F&BzTQ5A^4<}# z-XK<7gZKx8ulf4z3PY|I$*GpN>dwbNE^<4Aoh__{>!ttDT=4)AD z@M?v#@oFvyI=4&Gwoywb#;G`@Q~k7n3M{j8F4btUQ&wi_!0C8yr@~cygNAQymQL|C zy_T?KQhm2nLBaQ$EFJO5VKZ@6e7vs{&ymepaT$H{WfK{&jdT7!N<2q4vk>m}nEP_V z!dESKv4T==2g4}%iMcH^~ z`d!xRl|Qw1u(v}p#QJ9OZzjF+UJ>~^cOB`H`Y7G=ZdKUd#?cj z9L4MJ6*<39k7mig+5Ap+Yq3*FkIPf7yd@RZ1Mz5&PB24UQenLjzD(fE4z_;e_E@d` zS!>UBZ>PRUJLa>Ww>^vg*6Hdu2=4cZ{=hsF<+s{7;hajT$ZDO02kX|*%P+l6=o_K_ zI1}r_xNigc?hyJ={trZD$axxVXF1QNex|oCkwoGirZ;Y%5Hr+xXrb|eau5$uPSQ5Y zcRZA{_+vi>ig<|aowo7bV>~p?;v>r6P`$x33^sk*)@ZQtzpcsO3oZRlgD)_c?Hvz2 z+hEE?JXA24@)r-CZ!qO59%?eU-S90ic)7ve?#m3$Tl`Xk=Na5=@LYqBTK(r3%y&rQ zq4NypdnEBtqrrR+BOaPaX?_$dmTy3H=zSZ2AK3Xf^}O@(32AKEV;gne}P z#B#r@<+8q|!Z$TvsrM-TU9^`=+yBMrq1=`VT(6XMHLZWC@O6u)+?EQo>&0#}`Z;f@ z-V^?+rEjwMBL=e_N(H{_F85yz|Fz~U+ArNR#lWdQNP22-v= zdBMHY$=1(ReupSWnCBeBJwKc0B=%k>?fI>Z0G)6-{&IU>+ag~ zMeT3$J`w%Bw5vnlo)GGU1Y`@B3`Ziux&tF`o?H%H+y^k3Q-ejmp^8@dXmOwrZ8Qz2H z%l9yd$JU=}_wgs2pA6N^PZkQ_fM2S6BKg()1mdLjKkT7j%};(cKY>a9tNF<<&-~<1 z*NL8d8s{hLWNQ1X`3dc&U(HX>-27yttkpan^OH4N?yu%2GE4~ne>Fb=9~#6S|3&8~ zYsB7tI_D=RWG?p`=IboL-wnFuqe!4E zUyoK2I?L**93PN;Cp!oA?XL(wD+esUf0yat1UmNtk5ut(Uk>V-lkaRvQJ~|lFQ8ra zYd+bZQNJi3xe-z_U^tl@uZ_uBk@9GtU4%`w0JtD5;C!o3~B z`y-eiwa$?Vy*Tvu<5IuY`SL^D$<~kGbnBTpXIXoGs#Ufivh!2+-MXZ5kn0bAuFTfo zeEm4vkDIU6l6@bZW9pi_Km(F_+@IT+=Xsp2MsP*ujps2#$<9-D&!T*7TElg*v|~Wq zx75LPu4bBY-0w}dbKalsYEnF*yiT?MJs|B9_J?r)TjW>yfObAE zRr7u9sn1pJ&~mfyDE4xF#?NDp?Nv5JxQ|%7|Gg7*Jji&g2VsW|<=!Z~ub#8yxr;bE zXUTH{rR_Ygk@har@)O&CPq!{rxVj%c%J^(Qe9&O>%gy_I3Bw;)jNW%3j z<>#=~Q@S+tO)^A2pgy0GK9%i@+xv9IhW|d{{S2&&`T2thdggZG2fhVgmi05)uz?0h zD%=;(*Y8n$$zo@Dk=zp^ek|UJYmmx8k3cSXP(B(31|RxFFy#A7mY;b1I{|XvgLI3mKy%4-l`sUuos_X}T_}&&JzC;KccTUf*{twNZa`Z>2uj)IxnD`w>?D0PACVMb=3b z_VxJcxtkLjF6;Z2eum|@cCdTyp}d_CIi74iTKO`>`d~d;`;m>q{$39Fv=Mq|l>CWD zKCb0e*QL>V=ugJS5z0|6VtY-tPx&XW75<3+fpdMcO5^|}_LjAc{ z_N+FR`Cj0r!t0fParu+#mkM{$?|q&6>DDIolRfND>Gnp2<1*Xb$1T@ieon~mm-xM= zkWR?E-(O0%dVitZR_|#aX1Qg`y?p0X;Sdk*qlEoPwU9S4~kkNTieSc>$J8!gt*5&Uqc>NgYm%W2%ZZTiXh{&HN7;3VZ{tzWXIiGGFk&CdT% zWv87-dPNUuKGkO;VVeqXR5;x}Kz~=i`eA#$QRtXVf9bwVpuhShoy*_YZ7dJ`{u9w( zU3<7MQ+>}9+6MKpeV{$uzsW`1pY1AtT=#M0eS4)xzN^Xool&L_=^I746_y9Rbp+=k z1bwsoM){XX|CjO6>bH~l%Ea4Uc#p!}TX(1*m*1qm>?dn^ahd!n*}k=$Z`63{x1^^` ze#w0+g+sb_DBnnT+ROTf@jW$kgP+L|+Xr;NR{Lw8mY3}5QNPMR&a1@U(eM!OYQc;C z+8n&UGZxrOev3Lwz;k{+3H zvEje6-&eT*9n!V@6m)$MdPM3h%_er-a-(m^^ho;z`j#4fudJCjB0uOG1$~vJhG!S? zl$R0DrIJWoE_#G|*6g~mmoj{8x%ru1IsP9Afj)R=XG3`@t6B59qoU-)Y>t-{f_l z`uVzNvz)?FEyvg8MXnXD>W4eD92vLhZ>JoF?RtllGnsx+dWN(Z{cexSyV*CPe)u)j z4@Q?%810wST0%b@Al-XNe=fR95l*I;A6ffE>>9S~1U-Ekc150p1_fo<1ZrR$uKs_e+zcszQlc(uJ`XG1mzB}vH%XXD@ zHLZ`F-_ZV+?mDKv%(E<jInJ7PWeP%l;Wi%p#6@4gB}3%*DoL;BwOL&mZy7G3_ zGMSxnpNOoyucG-+_P*2aO1d*K&m5>(mwB#;h_8RidN=Dkh5a&yNwT;GtAA+cE|Bne znCm8)o*SlIWqNA({}O)(?S*)l@*(fAtNjoUf6d})cf`a0Y%uMTc-ZuB$6-rv?bUI_ z;GbD~(__OwHQ4&a@J|e;9L2-`VX*bPVfzkU2jwsxK5p?d)Q^XMY;a_-%gJ#o|A!WT z%wT?JCG9wB@b@jAc2Ye2Zw8NA{PzqVH29doI}QF^;|j2-BjTBlTYVd z`C=`X^__68e7l`1-{t4Z_iKHt=gJQl?B~i48tmuF4;k#|$`2dt=gN;5e8lWywnOz? zIoqjJ@N?yC$LhKAe#`IY$|(oFjxGJ#;{9CtPJ`VZA2is{m5&c`KRx$i9N6_xE$;T@r&SXQ6$Lb;5n5=6k zKFIftq9Bt!?b^=q2=9$o*Yzc%xTL&-a*&M|uOz&e;bWs#jtoA!Usf?##-)|$SKA1q zU-^FG-`B1!i4^#Dvs3dI8=k3j`FdF{I#=7l*T>V2eVn(M9qi}b$Idsp#Z0-b zW?OBUd^ znb-w(PN38_2I--%n?8UY)MtJU#7Q zr}a;U>WIIM^YwV{4b0!h`FCvhpKNwH|rS+x(rEIJkdIzTC?4oGa=-- ze0SIIupJzaoYT;7e@7>6vv?s|=~%&Xe14VTpZ7|QsLD;hwTI2qg#Knp5zkwt=^!^t z<(H?A7jXU?&t0kUazBsx=X1VRou3UDUd|I`UTHAraWYR;I2mO9HqCmD%6~FQzWBUN z-nFrG2yAC;y_(w`#D2lRwD6OoFoc-2;rDa(*ZBr|oERAor=LA7;&`-|;N^Tc@j!`Q3X> zzoGo<{BD63G}-*_Z$wp`iFw<8SpI!#4rQev|e^?=*Z!Ui{ z2*07%!+9I{9}h1yeo#*0Vaie3!FG*@a~99_+jyAmpLTFPHXfd4@mw#AhwBZFl&*N# z^m4}xgRLDp8V$bC(o;U;;R_6AyT`-NHkk4e4;KvXxBTZD%zh9LHyKPhi-#8&++*pz z{g)b?xAt@Q4+=FEAWxdH#nMV+Yy!rn339+MY zzTrPNxgLjHtDRhyeDVCD)NS>tzkJVT-QTci^OR)0NeakM_D|b$x34@Cyv*?JDSpGA z%l$aOZwmAN@IK`C@u+)X^}I&UEvueQi%YHTKUL&CIt=?F^A_~;F6g_XLvI%LxtD%+ zzW5K8Pxxba@1V6VQB03B@`1mi$_?gV#U|-z%W0_yG z-tv9wV^*$wp0}ucD3LERJ{zCnd0mUu7fv);s`Po~>1u}-4c6~nN)cIK#(?PKIiIRm z`!(B(cC?&WnfIY&1Bm$swJX zxB9zw(yr9h^Eu)XT|2SSIfK=oT>X8Q8GG*IF`#n8dvl|~ zF?%21gIkiyEZ$!uL&{I# z3X>1*=j!jfKyIQz#DRQzTfE1ktI|pNe&dVPz9XLF_5){vpO;nykKIsw#Y2B$ALDlC zz3)`H?+j`_&FapbxcH-w2l$@gKSJAr`axem|E%EYNn&tgZhV zKYk?of7$iE{-eKcKeYHEz%#z@A9Ql}{X4l!;>V-R-)*VHKMD6)s&s_@563X{e^U&r z1@Fynh+*hI(jZCe*IAPFDCYJ3jF*T)~_bhucsOv{BvcGRKLFZ z>O{YkzQQu`f4*w;aOWeRNPW`?@?)uQ;hOrU(JPVL3+Z2Oqr6f7qHUDVSpT4hA;enr z&x+eUNhj4mR4egk&X3MVJ^8#0n)2(YdWxlCf%WfU+GCHC@57UCIuFrbtS61g=f>3z z(GShx#$T8E`qHWPUF!RG${&6ADt+ls>C1q@&M!^Iqj}_$Msw2cL-9xZ1&@c~kEZec zvW%Z=`J=%j9FO`veM$V!5%?3a|2a$;bQ1j`+zC2m`v_wklJSagAKwoctaKbOe{?j4 zRh|mjTnrZl?#L0h;@vO=JgbSfyN|V+csGmyuWI7mFbdd4S>Y4us&N3N zOhmpoyIw2p>d)DSg#K?b`iIZ6*Fjt!%XF6C=$$Gb9qmW^li-`Ie;J(Adkg9fNvHRd zpEt0bb@&-=?6dl!eJyC`UX+W@Lz>X>wP?4zX z=2l;U-^UC~kjtonek|`nj2bXsjhYu>hd6I`_x45&^Y&cjQ%MpvOe5c6_<>GtqlOtg zci#)1fW8;@;l9Lh0{ULqcL4pJm+0`FITnb__Bol@cu@%>)Lhq{lBnBv5djr_2>L?UFSnO zu0QXP(!JC2howGCoj<60ru2{aUGJzKeS060`20n90CczUSPJZ_J>PQp?YsHcH&XuO zOTPQFJ^xDMJ1qWht=)Pm>)PWWgstB^LVxnSuEZbr>9#=qh$MCU;A9=ZPInP!tE&a+= zTYjh4E)BQSUhYJ`_3Di-L%zh{;y&DqNuPbB&&8zAKGKKlOVK{k=OWY-HG@9k1nmBn zi;WHO_d(te3IjI;iUWj<|9q272L|yCUzCEPJxi8v6&^AU$a;W1j|S(N&LCfj{S`cCB%juo#`n8oziWdw z%jnLfr0ZWRj_|fs{RID$buI8&;-$vdN28yrXAh;H={UmYte;P|e)@J_$nuEd+BZsw zC)P*KujRWzux~}uf%o1x|0szgKtAOAwaicXh~o&56L;Uusl*Y!EIoWQ;s^^B?AlYz zelHqdWgN3K6a16WHHqW9{=5DRvwyC?Lk_mo>p$UszMm#MV5#>14eP{FzWONjf38CR zzuzo&sfc-n?5AV@zli=HX*2^EK7;n=7JHn`U&TC07m|2~vvZv9M{v7>j-PY$$5zwh z@NC-o%TBGnh~q(0PZR9Ip)v67ZQWK;cs1~h8m91k`>EBR-^4h_#$hX$-^4h_#u38x zR#CKZ6!mV(p}qLK@k4mdxQ56#;3DvdwgHa>{~YLW4e8*`XOy1m=Nes8aZ@=*X7$Rx zSEO#<6UVD+QST9s)0{uK@mI%ttDoa_t?)&Ey5C%X(Z+7zJqEhw+Z|n6?0LT3(WQ;B zqf0x=wdYt*C*Tgo8_-Ruzsa6Q;!EhSL;M8AAQSNXhgTrU!tbu4S2bl5^UW;}mzG=^+ zCcsJm|Auy9+4d=hPA)D+zSs|vao6O+`Bxl=-T8#*=!%t(E@%BR@PoczhW$B6eq5Wg z4EgJTfBtI5jXKcZ!lA<^M}>x)kRI)`)P09sz9$B_xgF_UZI*^5_OGFZFz8ljXeJE0 z6&fxf47wE>pwD3mbc-4q`5yF$8tM&>HbQUQy>4NV@|4|#{PA}?HWG%M7P4m$hMX3- zPAc$rU1&-UvH4-oDrJVbbi z@G#+S!Xt!-4K8Gl5*{J^LBgYiM-5ghuqytOor&>O0dV_6-2Mx>i4eiHq5N zJ$Dq2L(bGs*eHR0nde!%vOmPqBu*j$2>B@-luCp<~`1n?u*Iy+U0xEyxpY(gm=#%(&(*{RR;J$_Qy#8I{k3JUj`e(l~p}S8v zH?QCIpJZPDBjMv?Ij{em%*`M6yxz&{wfw#e&n1L+!~S;VFH$j-{ehjPk5O~CrQreW z3zdCjgs~q~{7KWRM1IqHJcoLCuG8nM>Z|IP{&XL$+9%b!zyGG{@jTioqCR&n;^`cp z>qO6PyXg&mTJ|-!^xWp=+d6)looi-!-Wxl+Q*#EvR*qZYXp6z$kj(nmxu>PSQ-ts8NKsYK8tV*VeI#d2Ac?H`M!~`Y#NrI+`~&4@-F@mVVUgM`#QobCOH#!gGsK3uPIHwHMI z#QTtP=j71&olY+bd9+_Rg7)RU6RWS#3OvIJ;9Y1%Kf($0tI&#mh7;&_p%rupCqS1% zE9ewXjIut^F?8!|t(0rGzSaspgl>JU6?_UOW>_A43@7FYb3e-j_+Ds*9E1}Kd=EJZ zCl(1qj$E9u&$OR3%J3Ab5sIY)59;4Ql$2W}jTh?2@ukiVb`Dt2Se{P?BI3C#Zu8Vmu!$ha$udRBK<@50z*7%s9F_wyRX8zvy z5693B#7y)P{$lh5#yQjYsy|z6-mK1_uL3>R-FJ$9i9cce3(NMcW1dlIU#immS1n$J z`HI5IQ8%Ab_)Ph)m-8uwWqKg~Hu$IWfYtlZ!S84g^5OGI?+NhNA)I`@ze)YFG2^eS z=UbWyt|{ZQ=9l}NOy5*4w=X^Tpq%@mT(py3z$@fE>X~4lJy$%>xF0r2AE^JN7C`IK z_^OYmlc&E($Jolx+U>cH~*zxaeY|$YI6BV z2nfJJ})!*oThwESz77496d$cPt=?JYs=SD#Qj6Pyhlj(x1oIY z>z3w=4_I2r;y$9DBILI>i+iKOyD{*GCEN$JrRH*zXVpXb7t~fI^8G_kURAzz#5tLK z&wKeb-}OJ^@}0(a+~ixSp!RZ{_K)-aiu?2*xjYuuk;i8~O7b{d!Co#uU+v|H$&Kuf zvefNs4DY5LEPcbCch&p>#yyZJ=((rU=+!fhd&;_Ye8S$l{k)j~aCo-}g#1)4HtiLE zljEkGbFzB33JvtPs|xwTdyx8e)alQqQobj)(^a;EFk@eV(0;78*Wq^58{LA`?dNy< z>!NLZJ`OxG7Ijj88+xUBp{U7uY{S#Xr>_D%?>Bn(TYlM}Vrj;1br8Hu(C3hTj5|LN zP6n`RhnVg|KjhwHE8pAT>_9ml0~PR?H#~-j#{g3HGl{?TTf6SNB1#wal>L4>y8q~l zI^P98Ayrr@q>BkDoexwym zWna*;!Py~up2S;*`QF7_Mhtd#Q_l1LM9V3xKZ!pZEZ-fr)TigBpRA6zsOK;1M%JG8 z)-JDnvN+9?2CQe_kZD$n)P3%0XOzWBv|nq*X^#5+Vg0T@u773mn%8I#g22Uv zl6VF1OyVTv@uEt2yu-(X^mOgZ{MzX6=Bd#Uv>)CF`EdPC;}I4~FY@K#;C1UNyilLR zD=d-kJAqFe$02>v_&7So@wJR7hy210l+V9ntNkJSovpw5SDZt*pD^Zi(cwPAjPD%o zCd~NG;ZDL?mTxEgOu}u1>j<|HhGIsCn+P+$bGVT(<2#4z2{XQPI7gWAox^p6L3lau zPnhwY!y5=gFQUT%Va9h3ubBSkU%~j!;bp>%?;JisnDL#%ON1HUIlM@i@twm9gc;vC zJWm*QCptVwnDL#%GlUVhknbH4W_;)H6k$wb zqF;rBjNeS6--UyqLpTY#6b>?eGYPsC+`RykpljhE<2RF_d*LACH5DZzh)r zGk!C9f-vJZlgoq|znNSi%=pbDCauiNNt_lYkznO&G6%IlU!%4_x;UMETlaSlO zLB?+;A=ib2jNeQ`?h6MQznO$y6b?d9WWTP-ZNaU}PPP$#rZGC4Y$pso3@1AYGk!DK zO_=eU$v(o+^Ki1CFzi4$IY1b8!o_p8?zZM+oa8uhnvT#Scj@@>US~g0PkyhnA804P z*Vzx?k>Bg=2k^}AeWo=OIs3A=jxhR}-|OrL=#by*><8$S-|OrL=$PN@>_?;ZE5Fy- zk9xw+e&h%{`%y>O*^f4)c4R{eYb2_d5Fl zInM8O_5*UB-|OrL^dP_2*$?PRey_71GbYFRz0Q726L$7viZJcRm9!siM(#D^!s&4} zCs}2`x78#5i=}E$KBV_NjsbT1@5Zs%o@_%qZoEy$g$q5jClkOki3g(}Njw<+Oya?y zLlO@LoeHhACljD!A&v(x8s3FC9t=Jd;&|{0zIXB9Wx}*46Dx#iPbMG-g;v^=3CKyI z)y0D$M@c*wa+bt{A%}%l+LH;$X`z+&WCC)W#DgK{Nj$j4Z`w-Kg2nP?|mXAB4@ zItf3Ma5rJvlZigUE*{)ZnD%61fH3sFay%Gz!yk|I9v_vL{~;lVury@#gp`YL9QTv0 z9}mtwpoo{pgX{UcQard3?_E6j*od{iRy?><=2vUQgD;nOVdDQ|-mKq8)n9aY#O7Nv zPFt$^RPzei{-5f5g>#Zh9Lr#}rmOe8;@%>kUOIo5xU1!l4!1GIy*_>%wCSga$ED-J zZ~t3A9xOzcUswcwvfshz;o<;Mc8oCkCEvj$+=+f=hX`ZdEZ==2jQOtY2Q^sxiFojI z469}rvQ07kV#(8+1wSznbqMuf5q9 z7#tRnL^*=ReGChRpJ^3%Y8(a`g#aEmFVxP^JUy8?&p7w z&_nEp*%kk}+Of6h^sCpX-Gbc%Y+R)*BgGHxa7g7|*)qHiu9S=|kM+!z%lR@4Y-8_AQhtR&TbleS5hm$fIH3 z7An}c+b&i=caipOf%XmG_mC?FV#RZWyxXnolDM zQNw`2Iv<+$=N5#?)-Aac=ct+RLiP&R&k5 zqP?sUd+GOA?Wp@chWaT(_luvx`9zuhkI(74@u5!1BXlP{(&IyE)abaSMyZW?r4lsW zH&x`gQW~>-T{Z50Vb))o;`7uzxy1Iyr}2IV_f3^(-^OYGMCSAtpF6!AaECjWN;{jp zhamdrjnr?8&-?rw3}v_j`858B_ke4w5SR4nJ0$c~c}&}Z{bINLe4`ceed#N(Z%RRb zUiwNQ$%RO zzT9(_x9ykd-i@EPj3K{&D*K{P|MofP-*(g!?x4IibDY{C7054YCf~^yi36M8AF^E3 zz;P2{s$k0wrKZTEB&+1noj0TWbY1A~rA0e(pRm>2bs^;u)rfrnUg0?K>e>x??5O^f zxetBPKxE*-; z_h8uk=Mdr~{&V%OqC<#}l;7vUILe`Z!$aTaymfB5zr*OF{*`J-bO`kK>(}u+It2P@ zJ|#?a2=sFHBiaYOm+u-PendYrk=v%S`JB$<{c*whg-^Hd@YI8Dr6x<`dDso3CRekb zyq@snRr7soakZR&p;hcA!nAU)49gcmcimSoEcDZIi&9SZB@%ze4Q~LyeEphX{x56( z<1_H5Z|JlN{d{`gxz_sy&DW#1h>QN!qFvdqYvug>%10NkoP96yY5A8mNVzrY@B4Su z{~5yr`<+AT?Rxtq-2O!Oy^7?1D#|bSONT-`{jFD@>~k=^m+ucErT?DP`!3^SzIe~S zYkhLg#Pa2fcN6X;jD6Qpw%cIg2j3%JEceS1?zi_o{YHfzzPG&_CL!ITz7k@xFM;QCRCEjVUoG0S@M#4*kn*d)}t`~amkJ9H?l|I}reyZ{i@}7yP zOb%WOy&l_u)U=|U-WBO{XoKM!?c0QOV}Mlf)L%lUOG&5gmdZK96@8yCZX=BKn0)-* zL#)S$|HSuLkCFW(gs~nIUN~nM`*NptOzG5abZWHxr%or>xpF$4k9x*n|H6k%C#+A& zz3QDTC#EBZQ|3w-BBId?DNobvkvT{xW&0w&&qkAJT_RC&cCDdt!!nv~d7w8J+&s^d(=so&9XLeEH%L!kvVN z2zMJC76%CT5r%z``z--qI8!E1I?gMdo}_ddAF=Y`Zk670Jt?yvgN>+nVi@hxZ%Xt6 z-@^#`eLMMn+^N;OugU6_@g3>mHl%meBgHbHr+;5EdWA*U`9k(G_;tlj)7L__l`!ml zA=^S2cHYH{!y@dwiw6rmq3>MBiH_1=EDrHG{YCE6m-|8Nxw|)B?nf{tgCZ>%L8ObyYsUx>xrz*O{NGcA`$6XM$fT zvp3G~e#c*lz2Um8++S(>@NnnTZoZV)t-Ij(qtZACsypNZ(8 zV`fLg8qRCHc!AnGKaO?(SGlj{AZ&{MU*?~tchOP!ok<*{)99h?xBGDvFW#XBR=&4m z`DC7Jsox)qr(_W?@t>>Tm$vWPpMR7%Vpv4{MeeD=IPtj4Bm7B;?-^an={!|7uITur z`931_-F4%e?EThTLF4Ffz18FQ-}vJBTQL6g(b%AA9wj#K^(QTjP&ttpZoB_se1E2LKly{ zp?sb?CiX++?G+-o;SJrwO|d&Cu^$I4UnlYvEwOl+&L6a%m&tpduLEj-Zbv?Sf0dQT z`o5oUisi3Jm0wajaSzJtd}L1k)OvpWYRIj6Q0^WmzrJp13rv$-ck&h#F7wG$!bEfZXA_P?iev8BBYAE#gQ9E=YQA11t$ z@C4y!5&n?Dz1dMqeR@u(=&AElrNdVmm5wu{+ z>|FTY{yymNc-zO~fBXAtU*tXp)4RuizVlz*-^W5mrTKm)I8(_l`!AnhKgIb8#V6Qb zDL&TY*jq3@a@{-~f5`-qy;ncA(s}7o&;QkNr&`v!+&HY|`cvcdHsNob_$%G1azAZP z%DMZ>{PAj<{@6!eEb?*RJoZ%{n?YK_JqlrQ5%BnerE;J8vdYt1-<#I@-1_!xL34?J z_Zp;%|L=WW;%ERReunyM*W`3&Z^3ODL=Isc*lO~;ee+X{mB+5ZI}#_z*hh|i-wZu~R) zRZ&jsq;N8|%q;Wrmzgp$49#rh^1$F0rI&|S(ey*OkYvuC0 zeh*T`XH`FYWCM@<(s)Ka$iM3#>l2yNpTleR8o(<5{ay0B8?d8;`+lgSzxxiIwlnSN zvz*T{c}V8LqXxTsPks5H6M2^XF_0grXH3pzxSz~l?75%6-||D>{O1Gm+>e{+J*?V~ z=w+&}16GgRKVqq~Pp)6H*P_MQCZuxTuZ{nX-hMmX;$QpowtfFw{O1t&W`@W5=(l%U zs_ShkYHq)rUr)cz|7dqqKYrlbNs)6ECB^gK|3vCPhIIg02eUXtN7XYhzc>P}*H+;? zpo)of`$6}8n(zqwlg(Lw^nIf|iqwe7&&Ma-uk&?2I+gCvQ`w)*W#tmFh4WrzaC9sB;r3Bvg6s74YW$R5Z?)`!((B(+^im3F zyML|pn(8w;g~e{t>-E-Ny1$zLDOc(G2>6w(kF;Au@?C7>qpTmYKE$Du^Js`qyYp!2 z`qj?Nd3vGqbYX$)(}sL$`yUe>6!~O3)2Vi*2s=E+2m@btZZ7ZcF_gaOPvT>s``v`& z@3o+vWS<)2>hZpQ@|*Td;-8e~QEPY0wqLXgonF^gbr|gTyG8h<{QaufH0|GeR1U`5 zfOo0IQa^vAH`UM!=Kz`n!AT-FHi(eHHpu zM8AZ;C|?A9g};PBf8j4-@JHxwu-}iI%I5^=t@}|`pA`SoBp@B~JXFa2tttm5|9f6# z1=8Ok&ICVJF_HUx-e~RgHY}ju<8Lrn?mq-P@ymes%>kZ#4d6pF2KP3geALWx`kg-ZzbL%FSP*28?UTIT(z4ZvS0!p621|9qQXsbFbl-=-qFE z-WxYqf1U%$Ks_&`4ZXEUZQ}l=^Rcc?(`N!>! zr`px;2l?^PZb4PszgFr~x~asubAX?OdlW=RvA!K0Mf}U}hYqA`;rBABPwx8!y?28i zVF`5Hx(%?>T`9V2xBg$+SEbQZjhg(_FVNpC{oUv&_6w;WwfesN0gD6qcx_7Q=YaHE z`+50=YG*b*p!GWYUt4vr*_p6ZXU{XiVQbj0FD)le6FG?}SF`WX+A8zI$%D3^Pw}Hs z?4PVt#r%3*>U|*Z<$GYDn)-l)f4-^?;7oIl6;BWV4U>JEvcVH`|0eE`|gSxPh1>0y6c455x>0lC(2?z z==567v%r4y`v87^-%r;3GQ@~H!~U56y{J#{bN;vNV>CJyvgQ8robDSbA-~T1RnMJ0 z(ETIr@+zE6{FGl4S(kaP)mK~f3Ch*H!I|LCtRm+x$@-x6x2p!7n~}&A;;ct^B=8^g!#)$lnUTN#;oIe3D;&sY3bJ z%RO{j{=4#DFTbOsFzyXNKa}39?+pOoTz{Ng^xm~%{dRItxC?&9Dm>QUgW~-;X;AJh zHu{F8C8T-qD}1DW5AAQ_AJN~5{U`c6aXX{n-dn z(!!mhT=EnD0QSurZyG&4clKQA@f}&G@W&Ce2d*F9xUi)8io`?sq;_B}JAUsgSKEOZ z%}0CU+oAVJKIJRMkN>20;5hBT(Q-Qwq{b7q1KR&~A)(TJN2BsDct9zt`Y5JC_|~T2 zD*N2+@y)mUA5HNCb`RsFKb{{?<*UTEt~L1P#$TUrosS0Jgzws47svDE>hZAOg*EuK zyoS99#}hk!gYj$mr^=s%UxyW3HSU?c_r~Xc|E%6af_2EG9O4S@oQSN;nH})y&?5Mp zZ2tD0|0Ux}d`_^<@X7?&o8AlGOs*1p&}n$O?>PJI%%tSBk8-M#sB)SWe&>%|VC8G8 zuC}z2y>RwG%NnNcJEHYnC-wC(KCE=r&`Tze zpp^W=@%b{pK>ab9CmNl!{N|Ua{<-lJ*F}1H!gLj@I3rHm4`mI(`~7bXZt#OHeh(RvwWwenE>a5dP<))Idk$BPAnQ6 z?OVa~i3P&TNSooW_BMRm;9mNvK0ecekMBRKKdkclC(luNohBYLmX^`yJRMC%&q$~L z`tm&sI#*mj`INo)={rXG_}`S{^GrUbNY^n-{rn^A$j8MdA0sS3YH23;DC~KUvBUQF zsD1MJs&-fT=j`n34hi4x6h_NW=9^Yee82py_E_|^i2jtt)yLrPk?*VUSG##T%Bvh* zFLpJFt7ASP`%|pmlf~7Ww4PIltJf$!j3yN_^`_!C4ey(?FSRsZ{FbGK?90pglcvWunSX~XF9+l$wfAhy$U!%1v`(7>WsOqu$oc*t@dZ|_9*ROb0j=Mwtw7-9R znaVHboAP}Swu5ml%EAuHz9@U{{6D$Bj_{JDd9Dk){jh}^^h@q{10P!nKhk(&3GHp! zCczo`>3l@~iGR1>V88!$%2(m9#@}bJwAaSp-;}jhxgQh#EY(@+m)oFp6Mj^R!+%)$ zF}{iAHz3WU{$xH1db;oHM*Bd|WS&X+i06@@XIMo4+eKpCZ~Rf*6yf1e$Vo|_(fO( zeZx2&^D%p`{;U2n0pNVA^S95o=WZQY<7zp1;@cawKhEAncX1rhK}CMbpVD=DE*Ey@ zrV{K=;r)mc$i7meOSA_Im+|;Bj(XyC2aZ2CpVwve7B*jRX(qVe@b~$0BE`pM~J$(_hp8zi6%XWtA^KuKMP`6!~}u*O#YO z%h}1s!6lt9d3Lsv{OvY7d&1^nqKB4tRK42lY;+WUoKLqjexr(C338u5ezx_u;|$pS za2)vh<>%J%Q??o%=jf-*(@y~b<-6K=UPQgJZ@^%`{*}}`QTgWNPUWmawbf^ojdKfvhw6R%-^l;?)BP)YQ?KGtt^FKd zB;6NC_p3nnX830(!{dP9(NhH7YpWViE;~&6K>lREMv7ho2KN;I!sJupcxCi!(s{1u zC+I$NrC$s2-X-)4i#em0#F@*=r|C7n2E96}cd(y*>z1GLP~nuh@4} zqCIFY6a0Yu>I5DQ-M}l>qjrP+cE?h3;PhyS?S5ZW($D|KRQ^}d-tT1o@22vn<>Y3c z&&R;$S{X=`J{kM^newBY56i*_-7ls2RNr2nqLV&XI^AphSVq4~E1*M)AHDJX<%^(K zLtBbYEoF3SQ+r*KM&ze{b(&6pmg@J;RQ@!bJ|p>E9IK;Bt;K3O>-wMXfAn6Ybiw+b z#8XWUe7g-&{GQT#iHala$Ec;5 z0PA%j+Y3v;udC*-$ZyI)3H|7*S+M7c|2c|$aTJHzr}{-NuES9o^E%~SmHX74;ZF-QBT_9>i5 z^yi;jeEpdL-m(5n69zrqe#%6D#_WB${s@BB&M4?5I%;q{FMYWBgZ8#;lg&Z$OZ2D7 zV88!Ep8ojr(m%gY`a3>M`5Cg*FV{~!s^mxAFNBnRKP=ymv}|7baPT=qvF7 z>JRvl=nwcM`x-2t-;eGJ`qN_i(?|TfEp>5YH^1xUeyV7*8_$2j_JL2LUtgkpP)=g~ zg1#jBMY)Rg3v!(37hqZMF?q>vbMw4?##h*;Qt>0W} z_MwUT(P*ikKS%pe$=~l#`;a;}p9yd;w_i_ts@*1Qx0CI*m$lo%b}RXtd#&9zmT$2% z6Wjs2AiUOJvfsQPbl^O|$15FYedCv;Kk@iQ&_?A?Y?6YT_#hpXZ_l;7&+h?B%z*uL`EAM-MuZqPgzfQ4`zj5E-qZGv-&!!-fHENIQur>g<*Xa ze#-yK^8(1V+1pFiPGG!xKTqPl-?8V3Kd^#)alH3$4eqFd+$M3}msmS)ep*{~z+ive&B+sA-&Fq; zf2VKX5m%vdoEQ4*;=CchC+p%Ek=r+Zt2G;x$9d&)Y;E7;kFIMdf70tAZ!|t_LcdEJ zEcMH+tYeq%Qa+8t&kMPp5bqz4=WUd~c-}_&i|1{$-)Evf$-d1c!{^ah4>=+I@aJu+ zud0tfs8@ZYAItCUmdhFO+Kh4B#i!hUojC3eF_Ab8?pyZhIFr(kml@sXDBm+>_Ihd^ zy1!ECUYut6DN8c}(5zwviN?z_@}25-+!LsJ=JLI{Ne{$+EH~e$MZVi>FG-v zzYi&X$AM42_{+en>M2%U_MI8*)4g8eac=+9$;S2VM$dX{U(WSd>f)oBV4De^oZH3w zBHEGfAeZ6WqI4)hPBs44sC@VA&9!20ZYsXc@Vq(uLhFCN_wRFE2r}$wa4ze+iTH5?=8~)4|s6_jsHui()^BU>wgpb-;(OT3@q|<@#yv9#WFv& zbJ|e@&Z|d9gB0Cx-;TeZP4y#9H@Ck{={F?ptDShR(9fq&I?nrX(t9WKUy#awLiux# zyp*5Tt1g7ryEBz99dF+BAo$qE{O6?dr}^|;;ZqOgwDyb(AfLrXe)#sN_Ln>}ze+^Q3l> z5n;b{|H0@Pc<#m(cmCMqT=GTdTlT=MuZNcb*7qtw?)-Eu&zrdO)1lB`f6>M%$c-{j zejS--S~=y9w(nB$H?5ztFVRxJpKa@`gG~OB^gF)Kp*DEcQeIV6Ox32SMqZ{VsE)J8} z$2VE~`5MHn-S<`UHHgPV4fw7`zNX#~ci&kFYjT7~tY69Z2}akI-zTdbER$cHtBslm z)-8YKn&l&t|ERft-SXdFv;0jg-?wi0cWL=!kl#fAoE^t{pUz3?mKzri1TYFnBe7(I;y@0-j zW&NLL+{LY{oMC#i0D6|@E%oW8`w>s(S3JY)!yL=cAWio#_RrbB*gt3gV*i}|i~V!< zuZaH1{f?k#5%i9-ZG^!Ox%ZJU__fwOetN%~@1LtYD8Jt%wq54S?8mgFiT)1sg<>GOc z^6cNQZsRY=5_D938GPK0@i;Db7GOCyYwvwI9p-r8;sPIiuCzNo0>7+;_@|$Lfa5`> zxWK2CK8&l%x@Bq{#s1UiR$;XKGQroN|0UW1+U>3#@8)yb3y$kuJKkx}RZsON>nX@L z0sZK@`0WO}@iG$t4ok2b`R!Z>sD2^Jm+Fij@%iY|Cc-0Ze*@uR!U5qSz-OtHiGR{O zLm2&vnx_ea?osm;;8;J$4Bk@n0xR$0HnmmQH#vLE9^3sof#{h}|51@o?f_4AKX z&MU=xuQxd#W%)5nllI>Nelw03mLNY}HGjXFB^Vx zZ!X~zgsBhlK4GU1=ue^#^UUwY;RV8O99|^cY4!W~w^h)GUo(AZBi`+nCi1w;>XY?% zq$S|nRr3`-NBdz3{pzauOM9NkBk&2wL5Hp#U$FOyJOWPQuP-+~N&L&?qPFT)Wqfn~ z?@9N%iu~emtuBBj=xuE?c&t z|B2jomi51ha_jubS=pKpj<=A1O_ut6uczEr@|(W!X`S~pvV1+#BIY}>eR#zFB>I=g zv6Fiz$4)Pt96P-!m*ct$a(t1=agP0{vvmD^245-Dllhdrf3MMZf%badQlGvvwD*<9 z$)eJ?IK%RDmL~f3nX>jzq}qS0wZF{rC(7DiWc!uo*>_v}i!8sCY9HT)N!C@M5AOaM ziJwEy`F)74nh&S+8+;1Kp=Vt?rc(M1SjJn^m;9OZqpBphBtL&$rBhVPA2&4a>Bl+$ z;2Mo{BEEU^=J%N*=8K4L7B+vtV86b8t-oZ=SX@NobbfqpTI%!jPo?s^{SW}v_&d=a*i+eO%lE=q`N=+L!tlF$&%=d`h0Rx5{h8nwY~JSU{RXi^ z>QB4*&(lm#H_`rVu+-1LLOrcyho5D78nFC|(JK?&Xma4rRm|2Ky74`U8V6O1`t2-> zzoqw7-hV%2MN7=S0p(PVUob27h~u?eS9jyHU(b@(BUM!LW1nRB!j6Z>Vc$E>z&?ub z_HJv?uWx~P<-W2uUOzUx;7=rY!GG}UQU6l$s$_5f->1~x&KuprNrQTyl8{>N1)H{dYpb6Oczg!@DT2N_Z&rU-@vFq=Zz+DfC#JT#8uifM zpM`mh_s>Vvf444~zQ@t!Uuzw1MDXy>n_l|o+U|(qn=kS_r`)S({aWKZ=ZwbfPQw2` zH9F70k1adbb7{O`m`KywE3gzVl@3lXedGt-wd=R4J}eR60$yKpx|BxUVz1r}3#2C!M_) z?Wq2~{RJY2kJ3Jq!ZT2BI6*$N5{~!Ha{P|xVPoh=93LG89M1SN`F#IQ zq)$$5_2Dl8=VepTA+h3(v*>Sn-%L0TJK_2p#^)h1ZpZhk-U&L~d|rqB zQP_+Ph?xNAy>*;6x}8V5#pg(W+4}3tmHB0o*^TRGnH(Hlcfhg-?!G&H z-zbkF+9SXHPVw9Q{Uk3JJKb}<6Y>2swqpE>@3Cz+d_vxLD{(=Cdy22J{1Qhr*sovb z)Bd>9&v``WeN3eW%S@|@6SL--Z0)F}8gq7>4dM5B3FTY=CROov$J9hsc zNPWdo^s6*xsk?7E9Or#GYusBj%zD3=THiQ}{raNSJIs1VEOqnJO6w5LzDPU^`5RhL zKBjl0!9IP5QuOUh&FdaKpyT$W(dp*w8_oaC7mpcS$iBJEk51G3N1szW54t+KuXSG4 z#XN9F9V zU!?aaQLbhGjoMA;FD3FkXY>eTd7meY`;%lJ2;jUFmR}N2oq`>Ch~v19+c~Mn#m{`c z>Nw@%Xqr#y=;CNPD_ebDXsOKJ8U0F9NPaqxS>m`Le@a{!c0}oNQ?u%y>)&#!|0f88 z{}Q(&3_8j=Cxi3H227rWZbtutqt6j*-lG%RN!Ej@U$MVKIg9-rv=f%l&X#RBKa%)6 z7;k*K={!*Fsf*uTsrGby^}Nr|ukj$Y50%abb(q|)jt{we(Gx$1{fgIt*{^sVxI(|U z-tX@Hlyw86r@J3f{0zdwq%Zj6?vwWWp>ZnT&r$v>e?PrL`MWwkCEsr?lNXKqDIF^9 zQ@GgZkR$&Q{|l*iwN)dl)>$(_uhrjE!oAVG4b+cu-XGmeKX(H6NJsnV=T34QIRrnq zx53?4?e3{&{zJ&un}vM%bkMkgKi)Vx3>qB}HwbyoRqj!P-jePi?R-}a?p<*7m;EGD*AB$*5`PY`8;6x2ckGsZL+-xlBJ5rq2Y#gUf=5l?K>sZ@ zMeApxZ@+DDv~iQ^c{Es8W^d<&{;Gc(>Cyf^MeNHg+@9cEaYU4Kdfr-7xevz5?|QyM zYLCBZsn1X4pX}qbe6oMcQmt3NyXfa@(e>Edt+=&&W9t1Tc`xVcSP$w`KewqpO;VrIP3a)|Z2qCb`g8Xyi5{2vo$9BCrE>h9 zr1<4gUgX!>E4wc`j|P%ROG!u+OKoe9T`W^hnFoF+~3&FAs|{M7pu zkxM5>&foR<|Fw3ZkJqlnFJgKp{C`%e-XUq%=U2C&u5?+@`iks_P+>{&y9Kb{?w?CO z@joD!XX1V0S8%=<`xTsD#^dD)*e{lo@!8;V{{Z@|e1{(ad%Frh^EY6ZHD69pap!~m zeDAS*u!H_{_$za-#Gb?h`)1ZfiU8Px8%JId|`IBA?DLasCDDO(BbY1ey=} z1^evdGmw&3l_O_w^nPkB7fvRAP?O2=fW?Jts}?E8LyQwMu1-HZxew`8pg;XZ7kBKc zxh|%IG$Z2#{eyVk-(l~QaRRW9|D51!@v22ZmOquNe-%8n-x>K^;S1!*inFhN`Naz5 ze_rm1)AIYpM)YQZm%=g~7CwNEDnG05r-uKc`Sj=b*E_gu{##zk<;5SL68i~zmdyKBk88fZPpu&@ zaxNg&`|q2)Or_)nj*DN9`BAsSj;OuZs`EL<(R5!X;{*iC5 zR{k`xW7^JlW#3n{k@E!4-uZr=e1FFJN1g|$(_BERRB=@$#-|^+BPxSC)aiUE#B4@mRL;O9~*9w1v_fS=e`#L`%6I^Qf z{dQ-5g88&tFZoAV!*=^M!qN=%rU*Kze=sHaPlo4DKdE?5fxe|_OEbZ9tlh`rp6||- z{n9w3>S5b9)x%DsljxzPjt-UR^j}|ZA4EKf^E-{(3b`JPc-}zHN85W}KH7x7I^TEq zd>=RYXeV9UEcNp@ts}>uH2G*^`4&s%d^F>J)Z;i#_I}QPsPA!{4ELkB@h&X6_#f;= z*ADne9dhra{*v`h#B;-AnCB<=M|4}hldXH|I+OZQEs|5^Yw6|UxAa&sJI~QCfBNwY zT{qGEFJ%6&N&b8h>mfIluuhWf4{I|$N%n^|8C=-&VQU~9haVp8nKL-Lb%xI|uF3u` z!i`og+S5lEb}rh}O}LxyI|;WFmJZ1;9PcEoUWLTK&ija^h0VWXX(sq&nS9h!KFZer zO->rg=lU`^siT}!T6eugRUVjw0}nzoWQrTpac~@sA*PNn9NA z=6z?;1)u1m%zS+}s% z*S~q%=SpxKU1m7?7YgSV2Hdwm>`?FHk?g_SAX??EA+FfS5C)h65_bcs> zbNfBiU$|}V{u*Wa74N(4?k(GtdWciVkR;>mQw?_O;F|v{&mOVm&gs`*_iJ6-c;7V7 zv*`O@y7V(|(Lz7C_o8CgwjWvlV|#K7e0f)J0qQoi6n$z8W9W)O}}KVf}^oAdPk-?KrL4@@c(l zr(GJp7xErnjC}e%E2~$|?_0g6>Hno2f%O+PbV7bF2dv+v)*PAOhxROHi8bRYcVs2OoO7k86!81J*?aXlTceY-i$ ze$L6(qaL28=%_ji{`c`^;9ER_)FeXab|&bo_EvvUGxE9h%&1|>^80$C?W!D{>#rwE zd96w5e8X1dW3AQazL%-~kJ2rAracz=oo|2il<*yraGZEe5YNdKYd1Q?bRY3Lh3}V) zrRbvgDqg#C(qG2;Ts+X>F$=Z_*Gj6S@XzB*INio0)tp^BAQ-h(5WKMJLC~}Mbn8#J z-ICy4l4yRPZaObh`}OH3gHFdpR6_1Qbo&!4(JPq;A^;ZhyF=n!ICEXQ$4^%d_?Zmsx-=*D|uJF2ly_H?PXTblOy z)-UzB`R~d07yr)hOTDFjt2f!lJ7BP1UpMiqG|mhfetpER+fuh5(~W=55050~tUtFu zCB08p?gzlUWi#^mc<6eT>zC?BPUiK={c{1^$NnqVzT1D}*Q59P_~+>>t=C_#{W>;n z@l~1USlUr_QJJ2k@%kP4PIONZcqDPRr&x!=@eRZi`1*Ja>wH<{rP4m2mq8AUKB9k? zx_M4~Pt8%xH={ws-(7q?It+g~ETa9`A36>^dX7DSIC2qmSNk`kbSe=RJFrRQ!1s&a z^fJ-Y``WEPem~OmnAGoP^Ioca@gZ6KZ8 ze4tb4bW`yP>*vi`tn=iHR~lT%zPhacY5MI~`rU{29UT+kHvA@ZH@b@8dbD z{Oq;v+dX`h{I@{qdmr|7%YHVHI2aT$qse8#f+&=m#(A~vJd_0E*Pt}7; z`$xY2ds;8nyTvX7Kh~G%+nwNZGw2cTvx)abV4P0&4{;n{XFXKqDJL~Q7WXn6-#vYs zqa2x@Ou#Ox-TUMHR_8fg<-mh|8oMQQDOEbYA89$tUhw(Y@zvpw(CqC!BWX~0U{mJ?)^2O)8alWXw zDyOek`H?D*I&Yd=^Za&Vj~Z6lBl11=GnQB3>*i@8{Uf)}q}(0>;c%i2{ckn&9zD6( z27{Hz1txmFelC)qUze$p>gDOi&n5C>(NaJE{5t%sHGa;s{DP(K9%k7e2s*|3ia1Re zpX&u*yK4R+#ZS^Z)>qu8>c;uns$FG#Z4|19--OiJg-2|6A;(d+j`9S0C3Y8bme^g;Wi7i~Fa21{?slJ}cDIrE*CUPX zF6M7;KGVAc@)(`v><8~bYegKTwugKi=(d=C1S$Dyw_e*1E`lCrb+ zn%oBD+ltla=RZNctR%N3mD}R7l@ohvX(pH}YqySijt+Ui`cLVjMlArxv zYj+dNZ?M#T&(6j1!tJ1MSP%MycNqDC_2Ph`;HJNba?_Ei5MSI$K6r5u5y{|_@}H)) z5fH$X%e{wy_e~?cYYOS%F-ybyI+5N$`Q!Rl=*4FgulWASRJ_!!mq)FJp2U4z zKX`A|L#0m{J#f!T89mM?JuXPo1M%y^(H~pC3WKy`8)@GT(eCYY_HYsOXkM_?mp_f) z`F3SN;c@g+<7#69t3CPNvxHAPZ=A$|M$76OlKdx&Q(a>6JZ$B{be!r*72;Gk0H1W6 zO2&PwNAWwP>j{rSoa$V|uaEe3Tk7IO&aTV&XV0B~9nsFm{@(L}?H}qQ2Q8;^vQv1|!+jz;Ur`H`39dDS-Fm<5BPKo=hvRct zF7Cr~NNK#(4_^BoBk)bz|J+5!UyNtjl~g;Yb{;Cvd4Az4(78NrF@SL@o{ys+$$SXo zv^xjl_Cd=zZ^LKHHgN{z=ltGUcfX6r*I`}Hmj1f=_>{`+_z3z_8n)D@&j971(me{F zFgX}v`2nPq<79o8{fg(~>{mP=uh6fDc+N@Y9oD{l56j6)u}cHsMeN&EqDw67cXgL0($ z_jlq_%$@IrIp4~xAymwuP}XI1jwt`Yse@?pVJf1jnU(6aDoT{f?;rUj1P?r)}lkc%$=ju{$u{$_Ri=%42MRI1)F zsW&X4Uaha2^}Sx|Q#xt?-MJ?xH}3qC=yjPNt^?^>_`O!@lkpz-?*^V*!Hz)bZ1mg> zdMd0x#YcbYH|VeY9Mq7tUw#g1pbW3hl%5Spzm*=Z%&DGrn*P)>&MxC0_3b8W*ylr9 zF3uOZaD1J`HOD961N1}Ny(`6sM)f0Zw{99g?@YZ9J3VKjzli5dqCw8Lr9Ne~i&OdSDE=;9 z<@D8^b6M+r)12jpUD5m-tzOs_m5;Z0`w?RMQiNSA@cZ(Kykk7{@!61)AJs1>&)&JD zSidK?s$6ZLT>ZZFQ{sV?tEUS_`kv!#33ztZ{4(t}$79}y)Kzm+%5HP~=6;2)9j~?b ziQNY5<8SdYA@R{2gUpEeqQZMqtRE+n<7n*M{!(? z^n0lO&k?1Xv=jSD!-j7d*E2#G^Km&J4fsrybNk?Yy4GobCh%V43@TUJp9i!*#X9yU zXK8xg5Mdj*j6WglooD|W)eckteY^5gQ564sAZ90Mhvi&BEWba}d`03V{8Rg|mfruC z_|AU58O=vKSxS&Uw;&3rnca=i9GHzLVL-ReTgV(VyGz;N;!e zhbPL;7sx-qzfEiK%YC;)^>4uV)s*7bFIoTmdQ@M1{hHCZ2j|bpzJU%a7f!*>CFgT) zKz$znO&{0L+dGu6e6IN2a)D>3k6Sgt>1V)xxP6m8UT;jvg@Cc1YJG2&`jT@wudaA5 zNBtjX2ee%4g(}x>yyAGagZPO2OEZsnzgai|yVOxF70S=ezmt7ws%44?=M@}JUA!^b z*Jjy*hrjRb9J9l+f6r1s{}lIk zSBe9D;$tFz?tAGxzanvNj6!PK;Jk75oA1^ogm}mI~OEbY$)^4;{7^iZ39_&qYr-3q#<9!^b=DLU*|971whFN~$ zHsp8lLXE>8|JUFP`t)yiKKs>MIRB0Jll36K%FCl-~Ww_gQ~B8!1`;Xem6hR=kia+d&GA> z39t4&+GtU&`IV&gh%+klkh0vZo(MPWZr9V@8GbdO82)3j=k9pF^u_DV6gUQ z^*sqa-+J?G zT?bUYYx%#QR{AWk{Gz3WTOp6JK5-v%y#66P(_eTY1k25{ckLXO9tt0K>i>h_T4{I1 ztq{fBlS(DRC|_9A~R5Xc`m z$KXO1^p7@zzq8v=UgIL)(E6pbIeE0}tgqRNXb|*@4uen8M)F}F_!Jh87`}2}oY5nH z0QbV)DD?@+g8VgdSXzEJO1(Ox+<%sVLH+>tnP@)6Jbw`T(Drj3R_UQ95B6W|@;BNa znm?!cuh|V)>aCZ*T)j6+IemW{)1-U@@=JNGDSz#=0gD_p$zS`QVW8k8${@cOul8nx z_iJb5mp`Cd<-NZK8zNl$QFDvYBY!}GYJR>>aWdt%AJb~72aJu|xn?KclRk2vAnSRd zJ=c7o=ZgTR=ovLPGQai)_#wYsa-?us{yOFdf#dpL0fD+v)?bZZl;`|4=f(9{y$8+* zEO=QyutWGFPPqKEJ>%6(kMrv+OeNfPdFYzw~){ zALz8d2k)75Z4dL)l?p@XnXsW%Wc2?XUymzlM6JP+z`; z?`}m6pl`nPKbRkU$(Mk4)Id2dy^ZgOQC|uDh#Fvj^QE`)J>uv2((Q!1nV)v4p^q^7 zA2q(1@YQTz}| z+6dU$`x~Wsv)|8<%<@w^rY-Eh*Ff`26x02002~(EQNQ%p@^QSnRywcw)c)0LJ@QgV zqW$_m`8oUM#$|=Ad>h_7ySv}@GYdPR{)jXcub(NbKe_KG#>4tu8UifDw*J((e0^~~ zZ7)1FXRsUZ-M)E8kMKD3Q|Vy!V4JVB2Rh3=B&g5f-->qPapiorTN;k>v3BoUK)V=5 zgPyCA@1}#$fp7|ZnSC3c-zNOb$)DjE<)Hm?d(k^|siil!KtFqPjRqGQAt!P#sI?R2 zLo>dphC3`yo`h4+qLm$6>qlhq5iD7aDJoV>b?sT=S1$9#nSDr|b1RnXh7RF!)Ox}RdHd=~`m zh_C>!M(amB9#c-k;-cjXCze*@MLtG3C_-4uS@}W^40Zm%{@vDGyf0qyMtc|+;`U-X z!2X95^}y#5(jm%iH3TxjTdY2{yGjY2hc9{i?$xe!wS@8k_li)9?$u@%10r)DVDo;%Y%nIfA{65(tN!|49cm@ z*T0>bud9Vny8h!wbsdBH&Ut&qc{=Ag?)$nr&Zu7PI%|FXX#BUi_e<8`7ryqi@S*6( zJ5%~GoBPkH9}fuLkA;4`=oIzisI>pHs2_Jf#`^IM8LCc2KmH}9A39>He!SzM=m*@1 z`1@qO{*I~rx&;v z-93CGmDb&^)OyDINcV0_%9}fF7AZf~xWh>qVe6 zy~C1q&zmIYOwKlXxbOM+_>M`xwOzL#eoIc}9sXokf`94fA0eGG-;z%9r~FaRR_Xj~ zqZ9nsumnF}?Y#AC5BufTMK~U~IBx#l3#`W4(_Ug}ekbCI(Y`j5%luBfmwToN*IT}5 zUpwS^XO3_u;O*)yi(E8x1LnEqs9_lN=lO-GVT3UB#C?~1R{CY-4-Mh{g@D7ItaslE zp7T6P#QSB!o#0!v?*!g&XFa?JXUn-)*#o7!%6Dz`H3s{1cJt!D6aiGaes8Ptr4#j) z+Aa0-w@~gX$?wWXRPNeXz6ELgy|@{|phLuag5(^(J$LIiBL9TPn16}z9AWg=#Tmll zGT$%oJ?OR8IsHb#-{0S)_D=iVt@gLri1MW-OWpYhzyHpkbL-=u-KzAWKgaLu`T3{U z(f2Pay^5H3$@;p<&w6s~?#oK-9{Ao-B?=%vcRyD0p7!nt^2N^~UO`6{;$`6x9(v8bP!V^0wtNC{m&~&#??)te41Nds>tI@kudJreCLBihL8W z8?%=&U-d8Id1=7fi_Xgt?k5aJ$bMJje}Q(nu(yZ#V0Q}VbrXi&iO%aJ47(GZ*KTlh zXB*O6Taa!Bp3$C0gJs>`+KD#T6CNNeiXgw}Nu-Z?!pfgc(8=AuH~~56+WB=pCtZ0T zR@cr&d!F#0eBeDyby|<8dw&S$Vq8}WBLfZL`>d)BoTAnA&1b}_~)ge**|B80?Qp10Pl?!>! z+t07-nkO62UuO6(6aN+BKWO#&_2k&DvrmoIZav%0mF3?g`L*3j}a_+Ly`Bcbpcm?ulTvYY!lk%VGTl#$JLDna4^?fGzSKxgv z^dihbU-IX7TgH5S57G;6u(WWL=TYzCdDOvqD<4%lkIH=pk^2tOcB@AGMCi|{(*xzo z&!OVpL?@3ApAOKkisw-4;qT<*bGbZ+de`H34)uezqTi(vqxY$tL#?FeCqAs>=`i>n zpF_p|R@F!O7k`Jd>^`!GFfY167SNJ;5&XwwUIaZ!=0(su_r2rfo&wm-h;f)O{$4EX zM07N;e)#&W{)#_8JD_+>Vce?Su+s!7!J`dyi05bRgelkAPQnh47Q$1O%J&12-qmR7 z$?*8erv#5Z`4~KXo1u^z-X)m-2Ry^dJFT38-_NG8C7 ziCfPJd4Eh-4eX>_*OGhjjo)20e`cxjQGc?3+hDoh7w>ocFH39Xo=^Sx__^`kjhEM1 zf7T!GEAjtE>(2uFvuNph`x)c;;StcgL++_d=}lzRcS9>1B4ZWRLB;mFkbV@8{|Ks!?94KELMj z)pEF?`Mh#EU-a|8TJytg2;6>lec#Rc4$1rIR)y66_&w`~FAsB+A2;8AV2kSQJmqJu zOn#=V%W&-SEarmJQMQKQH6W4lKBK+pY9{1 zyR$D}FuISj{Siz3{6nOBCADTYJud$)GtBBLyE?7%%z3NId&b6T%0&tC&=G&P1$yk$LFWgjGLHSe(&Jbi zVQK8nXb*}czbKe?|#ghGCa|B_>Uki*1pf*h`OzmxU%cF@Q4dxxGQ zNw>3gRXa}J&aK`Z$aF0}U!?1>?N4KHPM&LhpO-Csno2hw>?!?);n&+R06fPrUyPdj z0Z)9|Qi&r1p8PoAL*174HlTde%yRpf{}A%^u5pjH*)h*PIePw|vz2ZE+M!>S;X0?g z7c~={Y5nezOONyqu^UJyK;LkZbUnm$AL-tVe7y~o@mf|rUjwhN7+%ZNvlUBEbsP?l z6EC1G`fljUz8g$DeYskqK7Et(?E|LY58r>d-t>EccrTt3-u`{qDj&)H;omVj`}n5i zv|s1hQhIgFdmn;C`3nd>89I&U|OcxTW>7 zoEy)yzIoYa<(@12iS073ZEP|+_~$jPA8VAK)AGdxpAl{69>7}PpGkc^^a~RG-)kAd z@zFAR<)nOALi<|&j%Ta9Q$9+RW6^c}>HbE^{)o?ct$+T6dS0S_KhEtZ=erR2DEE`P zjULWV$^@@9I2>P1H=j@K9QP{u)eroZj(hFYi%v^30piDgJ^j>2H$VB7tdED||DV0@ zfsd=I@_&;sZNWcHL#Lz>>`c?rP>coyEYWHT6iQVHl%ciCn&~t)UdnT*qHpZa+I?Eii{yN^?51g$jpa1nM z-!_iCgZbX6eD{~}-KTt48%N&9eD`Vo9ULaTmx#V}o&kT3k)&&n=npXOF#rG5GR!+# zxqV0Pfz1&*HeS^ATDKm?h>q*`r+=Ks1BMQsBB3L{u@GQv}=>6Vj%$D`YXgy6P0RFCnfcU(H1u&1K%bFBT0SAyQ&ZsBLJlfzWviP*Z=e^A?>^((Wv{T_5Y~+YrW|D^>y{4=V*OH>v6Z# z`*jOs+;8*3=sxXNu)O8{cb1yn*Jx{$N`9ZszcK{j;t6x%!!i+1Zn<&m(GQ6J>Tbu69<<&wmyA z930pDGB2_3aoPOyMAthi^-pzb2i8B0Fy9TTkGe8FdTIyN`2IWQ+td6rI81t96?kwP zhrygQS48OV=E%BCaDlWN>zC|%64Ac1^cTS#syKSc9#}v2zIhPtKR?Cggx?RV){p7> zChXwl=tqXfIA3bULZyF5__g)Y!EuE}4u7At+i0Ig{Yuz>Rq!vcL#Nzt(8R2~A4Gx-DdVb-KvjH4pWJ*OaBo1=}O0`iBm-f3e$R?Ymc#=MPvO zY47&_#bo`7`M0RQ_=?IiB4OBH`~$<)`wOxEY(m>fSpQ-NDHo4M;U&*)_XO5w{pF&(ERNZ zhUdo)DJ<>ef1=+(zgel@*;x<&)vA0i;V|iaTwt9SWox7!qxEb#X8`@-iPp2_97?pF zEwHQiwCdgb{D08VO@CLjmY?R(#YewztnpZa?YvI&H*iSzr3f9HKj?arf0v9uQ`6qY zeA9cN(yv`5_ZQQ>fC6vlS3u4L^h`Fyn} ztY^>^obUfc=LX6N+}c|BzNLm56iEfH3aaK^K#d-`D(o9D+Z%nBs+>7XHo>`J?seRS?VbQ$3vS zU(EsUo4Rtx-j5rf7#`KI418~Sl$ukcbG#Le<$#XUGNXN zuZW$SM?B=-$a3#gIs+UQ@2)2AnJlkc&Vd+Tmor}nmEM6ezV>T9e5Oi0tOh^3#hz(D zOX-YpX!YUm5&I~fuY4}ldXa6g(9V(`UC>8Qq5d8Obk2NO?f-6t<({lU{SJlqtNr&W zJSOn0Z2~tQVAz#^jM{~}4z#V}IJTba!OjV%^gK_v-_RiZ+kUd!zFMi|sC_BkMa)iQ zXJp3^G_*5<@r3?;fxy}1Otr6z%lf$;+Rs&6*I9si*(KwL@VtW7Q!4R29$%tArhi`X zOs$vj{RkS|(Bn6d!6CejgX8jVa%Yosl<$|7@qIXEf4`x=*55Uuc5}GQ{tl`Aoe?{y zg#Amjhv_BucSz}sm)KwN0+lcJo&=2}7%%DVVgYU6B~>H$ywEvmv8N&N!>O8!880}c zpW}lzxo5Ocul?ZS9UNb%*M4wux5CYmZ*h;%t4}MuO<>*Mw|g7o_KN$V4!idxnIGQW z&iq3UU!x;u(fZ|n)_b&GIim0Z4x{%E#uOglu%PqA@O(Cn_3-%jHgFiOhukZ65spjm z6*~{tL+)jCdQVp9+c~-Tck+_n1&rv*JA>>RcKH>m;NYGWET8*bppUY>*!WH6J8u3X zYA@CLjZf{R_J61F=MM}0$bTK-@@)QO{_FVm^TskcnpF;)pM8M&Zk2i%lJTp{cLVTG zKED6oNBsMPGHwmu_kN(VeJA-gE!f4dmMisN{++yooL>Dqd50vuN%B*dqxrife6IExe(!Kx=!NG~ z4=X&vA>9|Hu(YpA>ve00|4Q?wpHq|Zb^Vv_jb%FJ_9b>{>q4Pjs-N=rs6V<&@WTE` z`_b^dn5(2cM(F}ue-*|{v!?fA#GzP!7o}^yAsPSJdx2rP`iC&To(GT8HNVc|!+2V| z#1rPz^FvX(=36FsVLa_g;tBID6TB$h`0W<^%m{v%K3RT;dY)VULaf}=1P=Kw#Q4_t zIcmtQ##Si#;zj<-ar#K(2yK(z0 z&KXl!`ki3TeueuuJ(x4XaB$bK#*4iLa|R^-(w!PVtn}_vctqiQ72dCKzrtfme}}>c z6z)@4`r%;CHigCBf;l}52M=^>{6VF=PT>iKa|$0)xKrVAEoY6whZXKnSnMR2vqIrc zuJ>S0yTV5le~H2#rx)j(tMCkk#lZc+B?`A{dY#g1R=7doG{eEo4H_@@X6wV@`~K2B z(t4Dfmva5&3iYdB*Kyfi#n0(^Wo^$%FV;Wk`BPW#hqRtP`2rcQz901zjreFTP zVV1K@PhxIfsHeQ}2R;2@Dd|c1o%t?_Pfe40nYmG5e~-vHuS?)iucoKui#TEQc1dr! zL-lmA;+uYqk835~^w)Bg>S>O|m+7e>{3N}3j9}xF+^jUi`Nn5iZrB+O1-#K=j^^{> z&yqfqp72*@H@6Ai==~BxAD@Lj#4UL<3lg6?S?=SRxl`bI4`@EESKst7O*&TF_h{)= zJzS*triYBes^6B}kD32b8tdS(bMC&LLk)DF+v?r5m%}l8$+EpnFg<@r?9Ijp@%MV- z^lg5Z^gb&5XnojwTNr?j)Qf-?#(@$%CmdY zu)YGifqq{R<83^_gCne;;6Z7J*q0_bxLBS~V?6@*yT#?`CcdZQ z-rRky;)keM^7kUI-G>+LOK%f<3&+7d3ipei!uP3mD%`2*0}6L@=-!)4RaJ5i&q9Jv z_#U1IC)jRWJHS4jdtM0p*|5hesY~!D@gD7h*t)_g#24~Ey9fF?sCFvr3UNEl-zD{E z<2Ji@(%OA^e=gN8x3kP^V1Ax^U&j%u2fSCErudfGFXeRWFAJwO0X@2xboP9P?S5G6 zM+>LUi{ZUAh9~u0ICWMDUa(l~zK|k;>ACWJ4ru+2jE^3>C#-OqII{5Gy+wv64ik8g zuP{d(%#~+a`U3tD(wU7~mwWY4!j+Qj6;4efn&kv1h5C%ZM3?boxJ7vN5>Jhj@#?6t z(}V7g@md)qJUT2x&j!T{^n0s?xo1c`wR^_DkU_$i^)BGE91XN6N{`(GxSAsQ-nX5C zznc1IhAG5j=gU`9evWUF_(0E-+xf25BoC)wr0I*txSbU0MWLY{PZb5??rc1(sUPHg zHw#>-m;NurpDp*vnLg?JH$t~g#yikYn*JANOMixY44VnQl0rO%(`5Wq(0l5FQ)Jvx zmNL{mRw!ho04>7OodjkC%UNT;$((fWx5e*Aia;QweYVzNG6o0QP|K zOseJ~u8+bHs6n6FUWY`Vc7IA?ubi(h42eF%duMljm-7{dzN7hP2t4q##tUD;;s$|t z|5otyJYHe9jJJZ7qQAm!d7mv3zw8q| z6n4vd>U95%(*334tx@kTUhr!~s!aq>BU16zDyFb_7^9}8f?413Kwqsx4ABA0_WnD@2 zk0gW7jtxO+w$aqD;%gsWoB|9HD!0GuqaiIQSo!}?LPSL)(TwO1*b7E*WOfM(& zjE?_sGv_aMN_)2Wd|DjDYNE?_zD)BCN%=SE{qVGIF69$(rhl8@nOx8ZI7d6;cC}E_ zHxS)4{k%m(z?@&)6wQ-Xl*&czjOEgCHJ#7rbX!jg+jU0h`$JMsS6(e}D2L7md~K)x zF0m);FZ|uzQm*OoJf&yrj(f!3AP@5^!&5Kx{}&5>+~1kJ;l1l;i66E15Rnc)V}1|v zu)Nakkv?{ayu15D{QQeJSO^oQneix-OFg>r{_8Ib%$pUc}dEO42niQAcuC3GUO(a%I`O(NFw32G*Jc8Guiu%gS(+^^3NNgHJN8Xl)T!bjRPya zPxo0GTVY&D?DP2aS~`+u_~Cn$8-%W(lXC1m;OWFH@&y?#mhKfM$3TyrABoZ>pS{lm zJ*5fH=6NW8)xXfVR_DRfiFu~0^WJPi%dN6LnN5g4F#UplbBz9FpueG=Vu`;TT08jU zfag2UmmFSS>U{=-MO-@&;nc<`hr z^Wf_HZR%&YOBniLJy#O?+3l=GKJOa$vkwxVD6f@Jrt>`H=+W%S+AnMox~Uo&4=f@- z4LpBP_;mY&dx=iCzT7S4`8mO>?zd(BQN8~eXMI%ae{SdUzQyUGo*^7Pqo(gW)-de% zNrJz92g9I;hsNm>9JV!Zh;_hLXB{V4Z09pQPIt!-J=6h>?qM<>@}-?~6{8p|(1U*R zk7;P&KitE72Di%kPioo?oNo7Ppx(e4or92dsk=Fu>jM&uzDr@M7Dy7kMr}4G=K%P;Pw-v5uf*^$uJeaPk1pPq2+#JV z?Hp3DSn8GJDXZU(m|ifzvHBj(D<3qBK~+Of-5DAmQTu^u<-+&hrt`?ul>_UFuD($Y z@_&_D>t}NH;d>d<&N{VU-_R`en%g1ay8Rr|_v0q;BUstYA^ac1*3T1M($BZ)IN%ul zeY@lT%ldm=R~wb)#eVZD^!vl@-0!7k^m6&O&xv*kPJ_k)>F*uC2TACCXNljy*}Uo0 z??d9zjS5pg1Ig_inB6-V^~bH+|5|_DtndmB!~S?s=@CCT@96eRI6nuyp&axN|3-Tp z^xaO;|Dg2ebdFPDp-=CrDlFyGd#VaA(Q>6O8xuSsN8seRcXBN|uQ!rkr1^?Tl@`p75-C5|D&Z99 z>)YR~uCH*tZu<-_zg$l`Zcu$!8aI3y^t-!Nj?O8Q<1e|{f+=H_F_7x@`!H`V>j@!QWvD#za64{jEE;rK`A zK{$6rt~tCf8|@ZL5dM6w-jaS2^Az}Tq{Cl7K;w~|ez!5F_tRt(`hA$eYt-+_xmbVj zEegvySbuQ6!g3BaH+Un%`9@DeIS-q^N5&g;-bK#4HVR!D-zh9~>D;2iLN_<4`5T4q zV>I5OF4W84dWh+}{m&Ts9jBkQ`Pffr%h4YkW4d%6jKloRJ6JK{Iat-B>6N~x!FZ#4 zMSmD);k_c4{xzh>aG!$o*{%9~f?T#g__*q&Q~dbgXBF;N_)7}+Fq|9wCx-Klw=i7T z_eq9b`Cbz%pSn4&#|2vMqg1Yo-v@k|_VWIW@%LfEqjP91U-o2Zlg1llKQwftbwzo9 z)Rp^SOs`B=*DGO1Mt3*ME%FA9B0rrIXL*7~k)O_qD=hNo21VX{qsZ^_5BXeuLw=NJ z@;*T2nO(X0xLzvA-Ov0!H}F`i|L;`4^NsLi#~_c^A>K_t9-%4f~CR z;`vRaNP4ohGrX$-&u6ftUWT>akPdnuA{UU`JIwW+ou1Zpq7jCDy%C3nXGAZ0 zjwWH;}>)sTWow8 z!>*i=%WZFv8+LDUHnW@)T#sf?`3FUB`CH5F;n5hq8A@-V>g}aOuXvB@+i7=bA8xx_ zqxcPkpY%Qd&orh z8E3s8bg8O6JGYN||L!ZI{dJtD#}izYmQdd}3Nj3a_lX3V21#EAYEk}Ck_}I=_<;IH zowtSeKuG&3Hcn^xah~C%KMoEd&o_T~rFiS59DkkSZ7_QKS#EpZBY05kEjUH|9POV-`eJSO zD-Q^~c8o*U&d15V0`((q{20eedxSqv6H9i#ocSTUFW${J!1=75AJY0e!sXgMRndEW zhc*4Mri*^<9xs<}GunaEE_wj(7Cqs92ph+t-+?`@BZmFKW|2GGhiO%~PRm=OaD&3_ z3a1qoJHxpaL|{Hg|GbuZ**J6go6d5Nb3WKnoxbBo=%hC(*TomT3xA^z1B><*sj5XAaPU?*cu4>A#ezp@M^T?&M1IOlKRN&RzCronZ0X6K zEItHyNCzJ9LFX?PMRe?(TQHY4jpzyc%em(&EP4*+QsJCFL-ad$zQFoEf}df~vwI`# zeThKN!}=L2n4Vy+gueNWxzZlMCm6k9?nR8pd^4P8*w#OSxtA!c?RV}Q6gK+TD6H*l z?pqkfco5GGcdzHr_RS(b-XZZJpVEOxa%|Fc4oHr-@eg}X2I~lSQ4I8q2lcvuLg@SL zuOhwXgrCq4mXTyGKt?p~?5j@EG3Th`d{<3i` z+O;d+^WM*#Bjx-0K418L-z&BL)0%GI8MW_e&X;sM#~j=fP-Ani4h##Z)Z#?z%rHKFgANhYt{c#vS?c2Y88u4H6 z8oWQybM>x&X8)$=)ZKXQSM+-yu)8$Tu=CdTUCTi4y?66*zp}VT0fACcbYdai#g~HH?GmfFQi^uFIRip3G z>G&a)(0e~F61v$MdC!a6CFOgJ{-gb+roWW_5*?dolHL#xJ8gmA*7%gZr)TFp?S6iv zXYaAGf2923xtV34PUVr`0o_sAhGF`zDWQ*4;NSslK$ZsD7F_gY7Go z)z4*;Ve~Sgi~2!LqF+O3>pXjjUGR4cg|DOd>iQSE^z%Zms^0Z}_v7i^>K*!cC-kH9 z?dMbfZ*lbO>i>Et-t5=v0ppks=!w+Fq1H3_+)Xb|SL*kHhxE5Pbc1`;@1Am)?INfC zrz>$XxBRX|EBOe|>q@lo_%xOH7%MiF@Y${kx^G>q)B2}zCoHSj&uEnQFZc)O&7xmj ziSs>p##G{s?0Hg&B6~Xf9&KR@F-T7_Ed$uV=n{G4v-2)pi3Q$lf>Vi=`~{O#;`JU* z?3M9D`G8jxvh>~C3_;W$>l!)q3v(5&R~TF({S<|b4@gDpS%sHzn3e0ch#whQ_=Q%E z_qWJ+$1k=E+`EFqp#9Ynw!Tur^w|>DN&B#NRIHoN@i<4!^JEw$q=UPu~Fd1uyAc!9Pq-$FeW~JB5W9C9{7-Yz36ee2X$ILp=Bs0$yO}% z!N1Wry(9?(PrN+v5BqlUQL@>!-IGh-ZDjvy<0{|oi;j(djDLTN@a-4VM2*VrZRRj& zmvLs$3hSeC(lR~^>R=7RKTOHBqb7orp1j9rc4p_6<92E3CXc@b+Ce$$FSn?_Osl`# zqW&_i{&I`xJxHs++@k)n=FKd>>)(X0f980%AI5mc#*=Ql{u%9)_yf{U`TN9gB)zvw zeqoFD37K6!gX+#}&d~7=Rl+SvC%irdI;M}74bY0f2v@6~vI$;7C3{}Od}H2RGEUB+ zHOsi-;)T&T()42MK!D*7^STa{^ls;TcD`#7nazBP3Fj9lOTE~-irE9?*zxu9@r|9M zLb}UGaQ%Sj`;?s=`a`1saJ{Ea+nKHR)DZ!C!u6gy?UynfP2&OySw52KhPpII_`AA= zzH}GVULXz8(f2%q^kRjDK#&$h|6D0QNXxq04RseW9@ck=Jk1yJm-GVVA*A}Je>Be? zmhyDJ#os6OgZCeh?%D&+w?i-2p#2OtP(nb%@bg3^c2q%MHbhm8Mb+z$&;FXCdXs`#`dXtu~SYP$9(V#c1YKf1{2 z@7^Z)bYH{QbqoJu)yE~G2Y*kW=taNxNB65Kd{FZ5=~wuWge%7-)cFP`Gd)E~$>UjJ%2*Xys={$Y*w538hq2)`q_O8W`zUy6EvsI4QyUtz>)`0r5z)nB}QK~!JtPmxdialmkSQGf9PkPXiRpGbf4 z>8~BTzi{~|?=Pf&o^yWzVMwMMI@1t?-cy(Qw(&}!^S-Fx(f%Urm$bjI{gOKAFHCP? zzd`MT{zd(R`W1hl)JL{9S+#%Y$N4hDw|;`^2+ty_{P`4`{~5)got~M?CG-b6xxW3A zw4Ye6{lrPyPb}Ad;w14e!E*I8Fe-WirE^19n&E2wU%bD#?p!FgyuWk5XS)@0#r)=R z`;Y(M?{Pb~e))g0-&+C}Z;Ods5_2 zp&=WW=S3|HLnC>eUs}DIUn?}d9At>jDXMR|_ovWAhi~Y~*2;Z9g{D@8<$fRAzb?!_ zhvRKsI5k}ya?;a#dqO%tW_cnya$irON%CiFMSq|RIdV<1US{`i<<45k<>Z0q$Pq3_+ z!#s?ep8Pql;gHU~YJTlchNQfFrd`vezVjKWf4j#jpSeWS#Xj=d&)T>;pIM>lVo&*u z*q7ZCna{jI(?x%I?U(cSc4)lVX+9%%>ksW$KCjXABMPS(w)0m*f`hh*w#_3 zy##F?iZAUdXlqwk+H26(s<5&r<&7JX4`b?2!D-epsel=*@rZ7fXpx3FeI24jk;k2f=Dm+hNX@`ZT zmnytd`L}ha9SWNt-KMavC$@Df+^G52C@g+DXj`GM_;>m)k;3A)gSKXcWkEA&YfxDH z5`EW5VQEL?*OmWMwLCdLR%ohM_>iX0QdrIp7DB&1rs-JKhQ3A=ezC&C3cpC zzsKdKP7-^J`pX@R2pAf%{_{^IyBEnR_vS@%YJaJ6Wlxg+5^_V6CU#ej!+??|bNXj|vNJWI7n% zK+cxsQs3b^{KA_!J(Zj#^_={sw3mgOI6j+{^TdUPn-%^#r`z}|SS#X9Hx?W9G~f zAAey%`H}Zi{DpoAe}#qH8BQf%pmNK3_`<^5I6j+{@8cB~-mdWenB4OIOJV*UoIWb< z)=MSDPbIy-Rz8j1h{8tiLkdg&Y*NmBM{)@L+`>VoAFP%4Dsl_|Tw!_dBDe6*6do2k zSSS`8tQ}DJUQOSr@Ou>QS6CWeuy%*Q^Y7&JeCFL8(zyVRkH#J1|BIP4rx&H0q;&=1 z*XotdDR90Z)5m!Bo=}k4rm)yeJ|p#5Tqbr(_iC{H+c+q=w_WjtzkH@&;ls*Thr%74 zub9a&T-+{pPWOu{-g^~qLSbo7v@Wmt=1aZ;tP|@bqOW{L9E?A7MDt0z^oO=-z9z|+ zL+e65(R)7g0fnVK=QGm2i670kfYXb#ZjOh>e@cHqVet$3OsB#FnokUk?r&5$sp%t{ zf49QoXY!f#3h!5Z87KHd?Fx&7rE#L>A69(vTltLmF*~nX%*eQdzE`06r5_?YQMg(2 zuTgl0!V?OgrEs^xCo3%Do?=GELB4*^qnMF?(I1j=QZXarCV!|;^GiQ!_puZ+FH?Fu zH2sANw{m+cW@KDu@2eLx(od5sKF%Q(j77sbqZO7DoK&sKOu z^GnBT_uUjT(oxzxp_q|zwB37J%t$|P^QB@&#@)Vt52=`uahT1AisAf9#%V>JpV+yj zVn#Y@vLEGVuJU(CVVMWm{h-B+j0^32QZXa*23yB0W@H>`_v{xluTgs2l-}tIPbj=p z;T;N}q3{ufU#sv=gN3!Gv6 z0Z)bfhiCth_9b+@lbt4h0eDCU9`J$px0z`D1nF;kTi2Gno-@U|_buI<*_n2w*KM`$ zlOTR``j#z3!rR!}mA3N$h<{sdqu{Sk_u4r=&_z#%a?yfO?qTB3J>P|RqyrD>yE3JG zf!{#)H3Qwx3_2;29?*y7j8c9)SSK2f%`^XiRBPigTQ{}$knB6P2BZ8savg?m@j0FM zjY_ldR+Cx@)ytkFeseXInQB5p3m6hQA*< z{Dt^>zTEglIp42jHA z-nZEATl;~%;)(lrOGmq1_RbG~1vsxdr=#8M9r6F(^~pOBf9L7-e-O`a-nYN;o}^d!9|4h?@bMZb<>E~BuI!eXP`i*M=R2O~42k8`?|e+#(@bb+fW-fvr!p_dZSX!(LJMjkE@-BoOiEeT$)pUwLa}!)@VK|x0imJ|1-ZhQ}c)N zx%l97qvQ|XSB1}4NqWeq441vsj8izl-t$e-Ichw1-X&Wj>8a^5{F+ErV=*he&g^OtEsZ{z#c$1HBIJsUDIlKyZ||t zr$ru&AIv`Cw}6lI`-yLNoQn9-b@YcGq+gMa@(1`p2|a=G9rB~~Xnw)uto}Yw6VtJA zkjb%u=%V~e?*pM^=nLWhruR+gGfGqPzR8Qzk=|`Shx$0(i4Xg)1*8;u>^z|9|Nn*e zeYQ@K{{LlOW&QuyiI3(bR5A3p^O9AxbYtx!C*7LYMf>(>p)a2b_77y=T!?`x2&`s$ItYZ>shZ?l)a}zl`w>&mF!OR6Ezh zN=vHtB6%;U_EPTo-TeDv{A4))_4HTdBo-q_t!D*e~X`!cmx@S7Me{`n4H;rla$mtuad=AOsJul;VM*Q9q2(?`7^b~5#{(cusE zg7FUOWz4A;_=Qg`f_^V!`fmA~WBF$iM$%hWhWDBn9^^@SuPMX3DuxF;vF~uZ<$l4z zvva|2`nQ~P$c1&0q$lf%E}rk`8+h{>j~W*}F5dfNcxVqvPaLC*_ug2!7-`shFfQK1 z4qnoebu~Br8mBxPAGqn=PCEFW#pTGj#>IP)gJZi_RKV{<*^H&&0z;A)CUz5KDeW_1b5ACX*rT!(tN8Q$LetKLb3`BP~kNld7G^^Q2U5n%gDPA!pJ%J4{ErK{`A=(xpC0X&gUK{Y9Gn z1=67}o9BVP_=hz62c%=k!R#)V5g(DCBO%i9Bt2QL1uX4M#=n5E5}#x$!VmmHl1mYI zI>XZO0AK7JTuVAqlI#@)CetEV*7t!APTcp(#c#;?7wOPLlC>`RkrIw;DIFtrvuEIo zKcOG=07gG%-#?ApT_GcOm8JJl@wn%pC#c`T`1RAIVA#id2*tkpVB>qx$7ADWmk-Js z#@qdS@$&5)v9JAhwidNX<(vxZ!nrTu%Rd!b01R!DTu>rC{w#Piicd&=;yf=ND-ZmE z4{OKB2bPnbyw6lg-X~Pv&6AKflt5bN7Tym za>Qh@5JpeXAbPiXu)j;nIpadf2fDwZiL~hf>-K5KpTmBz-p=djv3~T?Of>ErRXM`- zBGsq84}o&NNRG+&A#7fV_|GFg$LxADg{^S^B9sqCKo7om1r+dkrZZjupJyYU?Bhs` z&xbM*pZ@(?ujh(hvNc7JrF*dt9YqK_&~u>sE#Z4GaDw#s=SV*IU+@vAUE8{W*?F|? ze*@^z>WkE#6!=W;TA7fb0<`Ak>wO;1>_Yj?g+0=+FJ7w6jlIP7DK(6jahd&C3$ zqoilV6Dj(>#W>5GN~B~zh6>$5@sJ-H0{-97#SNpPE-yPH^0$Zr9`J#;nYOZ+K9z6n zHN3}J?_UU-(}Yd>v+q_nH!CdrXF+qT!eXBRF0rGh*wD_QwR`^oDv%!kUSX{IzJU6j zs`~=sXCOzK=$ZYZ98iWlABOze{`?$8(t~q^mj((^3m1uRC%KFED;x%WzXD2H^T&XMw6f7Dzrb%hkv%Q9zP2|rL5OJC}w``X_8CZQYlPfg0V^-tQ~%laq1zbIRy z_Ag|pAEScl0Y7W0T^YY-r(yrJNHW+s5$$7uqM#pefcz_<4`HWbI|DxOYMgdv-;;3L zyWt(CCFXd)ZtH5sXXvNZj)A{}%5nKRpZIduyTBsSfd_sjXp0*m+C$jx3C82A9}3cG zg+;(1-K?-62I*FXrNao)+V1PxIo`cr2O76=N+?G?qlNFc`tt?O))1rguzo3{yT7`e z;=}yf9zuO+zvV9zK5X9UFB3lPo*RFe$U*lkvLE%ArHLx>*C>89x|eZG(^dY)0}8AB zjRzH0`SreA*RS`He%*ENG3cB8R6CUi`5vMA&Q2Fa6mn^br7>jU1?=a;cDqja5BG6% z!oS%o=**(}_SdbD{54bnJ$|m8Ll^IbgopY@eH7=UIoI8Fumhlkg&b+Bx0jXBow~`hz}^ADEE)Hqu|Pjk9?{_YD3K+K09e z^Z%*DrJSBhY-joHJSpBMN>hsaU5q-^+jd3|*I`lKg?JB9;{nf1QTeAVVLO07>wAfI z+LV>vhv)Mj?C1W@jUVQC*G`sDd4Yb91o-V2OC5p(`yNSftDG+iZtds%(3_-tTs~lw zV)utK9ml`gJ)*z^b!s1FeDsrib|0zBAM0a?i{t%oe3uFG-%NhQ-`gPVV7N}gp)`lC zo=7};Pg!(e@vi@aD4@5W>>;wdJGsDA;&a>{AkXJw<3{u@$OrmyyFvV1vH|3KDwYpD zyXC`hJ4dkPM|?fyOX`f`cd?US;y&-~%s!X%1sa1^x06Fv?BqaM{fM2&IkIv)5qk@6 zJ;?dYUVwM_@e1|_q3J=phX-@*ukV6#`!oFjA1#gUcja;_*&+1n+F@T6J4F3GA9lF# zgzWI6Y@bO_&fAvD)hhP8ftaJ`MEt|w#OnQzj#=+gzo?(O68|9e=(azpcWM9S^)CJ- zxK->TxOE`3r;bYXIH+<_xvkW0j#ZCRKZiql<5lWW{LXXtCqJeM>*W2(ty+&;rM_~J zTCChd*`uIrw4tp)Wum4~zc&z2nS>eK*MFm*&S(dOpzNZGY44Z(peWEhMGy zFNF2lUP1oJ${kdYQ|xu}a{krxEa%VuK3W&C@w>0~kxIOlD>{|f%=H`DgY;W~#hz!d zU7@{ol;|hwZ>1kCujifIkE;AyF8pR6>s{J+xnGw0aQ)Fv#uvK-enZK)K-+tT@0fLS zKCxTZ&f?$KK*tIDc{{he39+wXwYMQ{&)bO@J)xaVq8}e6{YT?jS*J-Q9zEfHJfr2N zX`BW5kJpbcBYe{z_?=7nlKl3fGq2Kn{!!1;Pet|IU!|VK51wc}A7XtPKd67H&jVq* zE3Hpy-$A3)tD8^ivzpx;WO~+~-F(uoNA^?NuMd>*M=p%~!sPAdf)lcv$JJl&FZ0(@ z?h=l#)E-6uQ9VsmsV8Z-Ct6QYeUy%q%IiVuFRF*q`m0_KQh!lBl=KtUeg{eU^uSKU ze@?z0k|$IT@78*FS6MwYQ~7Sc*$%j@-;Crh?Kg#AyQeOAQ0yysQ2m-CAN-JgPZFPl zE78Y2;)h(nCH>}Mu~&x<{FmEr!tbKpf0wqD{D;L}{Jr9@?R^pJA47jOiGFh-`LoC_ zBl%1FO_6`Ha?^dMW%5Y9PhQT?JBK$E&!_+d@^b7Kc4*()&+KOe1zA>uscq926#D+ruC3%+;5~3GDfp=n^Ar;AbQ;V zKcoEE*BPMrs2@6;>0}d&fllW?TRDvAG&Ahd`9AZzLh+h7bp2~P#n}B4{<j< z`r-b;I-zgpef@Q5#pi(5Yb5+#tYQD--zE|6_pmzAt_y;P{QpY* zxSfN*?~adv&K{v_a@anC?GJ+PKhGk2 zqrn5QLi;fzEXT3-V{V6j$R6=zb^HW>0{yt_vP-EwgFdklt;=E)ogTaIAy|}Fct4jL zEE4_M`&hvuNDur2nl5%(JguGM?cNW2U#k3jrJbD5t=~Ig7ejp_r>!pzZY#s<1Kz%I zj`wwcp!z_zR#rN067wVlzpKc*tN+^_$J%Duar6a4L+63X)J^ZH)M>M54*^`Ji}?a#jV9OPJ> zXnkGG>V(>9-S5~=KTj@sD%xq+3AWQ0quwXcP7lygssD}bv<~$;iFWz|;Qb!j>3@Hb z%9+A;`U|38shzI-J+;&COtE}l1O4N*Q*rm6?Slo4&EkKHYs5d#*Nbzvq5W{rjhqzucnu>Mz~?cL4d#pE~{T71XZXeBj6(*Q4Kt--QThPb=UD z3d9gS;XdSI<-1Y8r!&aO?3eqHU;E*Z|Jx+p?YD_#rvF_k!@l=-tp55vuq$Ds;=b+< zrFU5+KhPlMTRV)-Pwe1&3BvOe{R&G3();HMYkzI$CrG{ok)-$gIla8UZsmBlzy1^0 zjlQ2Aj`s#vl;Pb2IURkX9#IdUJ3{dVkpulJMX~;#p?Kfl$q9a;U&1Nq|84>QQ|LEt z0Db$8o1I_CiM-)B^*n)(*RI@sLvBagB_H(oCR+b@?Z26lCevR@Q(1FK9Q-u!~8wn8K@M;8en7szEAeomi%?UEZzR^EeK>mU_%HJB zWKT3L;&~E6*v)IuzwcY~Uj)*AQ3r>ZPi@}1X?^}QnJ{6!IG4ZS3gB$+y>xSM8fByh z@f$a9x)i^2H{Q4|={-tZfRD8IF*<+Rm00Tin)!RD_j3*(O8kt%RN~8tpHk@ZH$wY; z;14IPF;77V{+gLTxj)6R@o@@W`LS<>^3ilrK0=f~gUgq7EMNDnp!cpV8*?b{ z#*MdjmDI=jD>fs(_Y&~&LDj=;-d9=vk9%KX`JYI9IVS(O+6QVDRq)F`=x)Pu8K0{$DF_w5HR!+OhIaA9)h;n|!<*eX! zJb!OHhbZUTjdI^cuD5G_a2gS#2k}>T6}IB{#w}gfCcUS${O#UhLg`9;()%|GQ;AO| zz85RMPVMX^loxU$MET$3@*92&n*QE84pGk=w&ZSd+rj1=x;6o)W6S0ZuHHBGUc-8) zzdZ+0g^Merle4hD{bpd~G505t>ckjmB zjVPb0o%A8+?-9Sb_j2ZIBOxTchqe4qct50Hsl=w*!xXyZ_G^99ayOTY5aoWH%iXE< z-Opj92kBT)-p#oix&GIaB3wOe&G&Nr=1rGxN_y8TAAjrp2iM1qwZDkf$As2Lv+{uu ze9)&r$nNA`D1WcqE9JKP3%_65?vXF)-4K>H&gI=u`>(O`#;L^+-T`jk{@yVT zA@`Pzy}n!j)Xv%Ndb>Aoapm6B`$m=#30VT1-!^swWzDF@Ffb-_6Y564g^{sHfbz8@uu){&4v=E+fCS zH|Z_c@`k<7vtPI=ae%@SdsM#=_rKj-p6mzsd;8fhpk5%P>wkO6QJ}mV$x$S|Kcbcm z`G&oJXZdbS{4ge8XO;eDEtl7=exQ>>*X};>y;8e-1(lQZP7md)B^{*_*C!6d>U&J< z8#UWy; zrCe@<)?-~*k6kzQ^p@7+)fiHczhAzM-dkfiQ;Ay=pXGYIGx51tJ@#llVk!eU5yEa> z!R2jJ|K1bUBdqlX$8Nd`*KS04o2epQ`*r;N74+Vb@^NS4`z-hUi66w|ZrA#4Qa%uZ zkC!qZE3|&w%j)-yvHC^6q&Kd5zAy1vZqI*__*|@hG2g4?$LqMfVXfbRuzugTu}CeN z+I4Tf+x6pq@#Y-jw-ACWztetK(RZJepFdAvq?1Z~F!8NqX?q$||BfC4^?(ra|MxTS zs|VD-k8ue9uqC&V`;Xo?qP@0I4CsD1@k`e4#}mJb={K$Q{4%AF5cGe+^qaMw(;T|( z=h2woMm`!hsyrV{Jk8}DNPH(&-h|rmOf3%~%KILdcSP-YfDh%)9+UPJ4604-U0uYZ`w%h0Ng-N-%&Zfm-qs=zh5Nw$K)8(dO4l&QSS&L$0(O` zKJe#CNlH9wBYh3`wi?y<#*@(`lDk8pWA zwVm~swX-v0<96grdPUXa(F8s*kxJCnB*FV2CDMcSlL@V-QwTww{q%5%a&9Fr>GsdPtGc)x z8m`fJ;Kf?b?3#&~{tl}C7OFf5A+8{Kj~8M8l6>nA>L*v#e3(E9Ph`gwhq zUv&GM&ArRn4{hGMp*!ilk>zpgrCsX<{R-+IA>_z2{S{g-?P0xa+ECiR(0uickZEJF zNcHkBBxz~A4`_XyN%5eM5cF4zp0qyX9#Yq@y(Ly3$VcBpEaShYiXVGD)8D4`(8Hnn zv5Se6cjM+F`LS#nzXz+d_hn4)khb@O9HL&)-{hTfq_ubSM@g@@oWAOtRKWT`2z{I{ z^i|&nIRw9(dROGYZ}G}a#4lYeLyud3tyTJsvzgu!<+qhXSHI&G`;DKJ@#|^*VdVn# zhY(>|;>`?vpaR|Ok=ie^|t302eW_tyGW4vbfX1L{aXg$Qo2S4R<)@VI+aOl=^ zPi*`pe)+a4 zYvR*IVyov3xmk^R(x!hhAdw7D& znW6UJx%OaoN9#0S;&MvrN$nv%@A{b3liEWEhwvXznmg~J>fa2an0LAQa>nb+livHv z^wp{5#@9U_;c~mR+|DX?h|z3lhs*IBd{im_^-1qtW#y;UKI8WDAeY~)_L=68{pL-j z_CxDD9Vjxl6R%*0Ofd9BUWINBv_N{k|%G z>77h}hqjBp&@W+qi%5Bwq&1ZE2m$}NmE|Pfe>U?jc-9?nN;|xU7S`N$xVd*3w}ay5Jhg*2l$AeFWgM`M%OBSEHozh3ua~^6 zJI~zItLvo8Z|ZUF=JNG7E(4#mT(XtE`&?G;h?W~4->l+t_iMQ$VY#b%x2|)?H@#c7 zvL1SAAmWz0{AO8Kq-8wv+vk*(>uGywCElQKgwXdTT<#2QFCK@kKh4GZP2{6~xr~0D z(r;4w2tj`d({E7vb!GJLjnPLwdheJ1miGGx)o-0a@!%gJ=)a8VA5y<{F!WnnX@Lzi zu>KaD1}~xQy$d64TF)W`*IuG_cPaU~XMj>N?ikndfD#e>^PzlCC_jL)?_R zk>=r1zZaEr35{?5y{w#8Z5JdO%ZU(j&g62IXuD_)+Xegwh~#d%e0_K?&>&)DzSFMs z;`7EN(_5kR+CzF>wBCliSdVk{6O~iiuU)=L)*EizxW0qtm&jV8xArRi&98oi@xlt# zTYIQCn|Jqee?XHEdeEz7|%ZazE!(7f9EvJJ+_Qy0ocH0%L%R2tzu`>Dt zYTxmB!?&3Ju-f+khoBFyB{lA#9!4#eiGIy`m=HRvKzOQ)a%A#p6zGjO&e*P%C5Ym{(7{(Ig99^e1s_faV~$G z_BTCc{moOc{s#Gy-aF}UseSY*{c|ZE^bvyoN0|N&rQcUZ|CcfP$Vc;k`ddo>pxQr< zB!WId(BH%K52^hh;p=PKF5`x48?;Vgy83X=I5~TGtQywhhO-ElR%0eoG(E>n$I!pwu4UXUrwhK&_@XR z?_&Dh+P`#${R{U0-1!{#{Xi7^gRZ>P-CTnBjkG`L%1hl1`wQxpXgy>dP)g(-)A~K1 z;=vC>@UxZqIiU4BR#v|siOq+RFTD3{kN|qHp3|@G9v&9-5rY0kroU6$eLsh2AGA!# z{I2c7zSA_2so-BO=Xh7I6WR{r{{Jmp&Jk^g6Ja|n-CvFTKlBIUSl@B==&a9PlJqVt zE5EPGc;yXT{tm6zzOY`mQdPMAC)!tARkAKl;}sT`s*(CVI`l=4EBCn8-^(c;`b7x+ zF6D9$YyFLv)!z$Z{kr9QI}JRO?8QpU8L8sGTe+P5T8|?fqMgz_cuT4O4);&5boE-= zZ%943^3`d3jL%;(TyBH5$GXrzVDRA1UpCW%H${29)M2{iI`w%)fJFp)>^|k-W`!S+ zb;Umw&>JPa;tAnJ0^56&`CH#2e!;bW;@>^@^cKLkNj}umDC~yxMgQph@Ng&xFz7us zhUtC;iKqG{*Wwiy9|_?!@br7#I4|LT=O9=t{>pvtXPC+l7K{H1^j;2nkB;fNcw^vm zI}xV`e4a;Pif^$ao_BGyH=UT-MtEqq_l!IKkN9{Mp)^obw{uSw?y212z@WR820jRh z44sz|IfKR?=Ev9f1%k$Ig+)I>W2eIX90rZs6yB+DpTYwQ@8A%69w5HZPEfO4{*#sO ze*qrSTT>B#$WK|lVqKd4lHMsDkFV$ZgF5t1fTjGPu18@hKd9TLu<#$$^(id!1$8?V z7Wsp^euncsJ2`aki9tmCo|qgO2|ZnOkCB`sI#tgREzomB7wb8qzxU8iQi*@#`F>=7 zhgeUj&yLwtS^Cg-E-40d>-F5vd_DKmuIGNf;K{k4aS!KyD$yI6fszs#r!4ud7tKQ)AKP|Js)3|4%gkg?hf_8a?0g4<639RLWmh zqEA`>=t?Zn^C+wIJjxe6oJXmYzXy#`9~$>*`K$Ci$@K}GC#jULzeMhBVLmzk(Vf8g zk4pI_O86~=`Q#kO+Y&g(Q7PY8Nqt`*=96<5>k~M4Q7K=#M815OPtHSp$isPvO8Ht# z%9C}GuEZ)m*YKAf&NWoZ*I!bfvW|~@az5b^59bpq&4C-6ZJ=L9O{t1FRD)<05-M?Bf@|B#3M{!00JO5~G^6;p}7@??MhBOdnW zE9Gl1k?&mPbHtN<_D4PJvscPDP$J*in(w20@-LNm%)@?orF>&0_PIdw{jI0_+#dG1 zE9Fa<$ajY3`r(rFntg6hVuDoPxgyH=V8CNQogYg`4XD%v!3kxe%`~rZ>4e3Pxf!W z=q0HStdwt}M85xEPm@YK<-Lu5Rl@Hnskfgf{uey#r&hv0Si;v26#w%c_C+h<50vQb zX~jR_VSlp{K3}TMSm+BpRA5o!r!N> z;)3u5?*Hs-XB#hH(es{T~+Z~OYpW=#p^54%O6(7>o38(r7GS) ziT-#gt73Z_DZ!InOonUKMYkL@(`C@kUDUUR@Qhr^K!~YbpWj6+IIy;vDixw=Ff9p60Mim zeF)FxyOMuRRcQCOgZ_InfQ50u;K9~so{#e09V_pP>C*C!5F9UWRH~cTN3hOd_ny1@ zqGO{+gSO@gO6L2#dm5wpSkTrQ#y|6`Xk8^}YY5{zO7QDAKG63*QI9mNp?Z|}K>@>J zg2puCh3|iouJB;p=(|oiSic57)^C37#6!P7ZH(j%zjveS4tBq{=^glYR;Yi(AE5tn zy%kh%*;-lmHG2YohYv-10Dpi$=QG5QeU||1&Ge7tJi_Z3$NIj_!3y-yBu3^&^#J+} z4*!#p_uA9T>f?Ywmk9mAEH<3E@@r$ z+X5@SGj4k+6Ij~(4h~0^->_Vwi)Rsq%!igcPx-ccSZBUV@Irn_81U0_)jx1~N$=+z zE?pEbqJ4L5=4#0|o%jKMQOipuU*e?+9#y)A=kHVfDj$VEll-ZZksI|OAQpZohuFobE_^)kFdq(@->A;}3 zCna=2XXYBA8`+OA?C%o$PR-D9fwsHR^@2ZhfrN!+5-v=^T&)0M+15e?qJ`Rhs zIpFpLxz1?DU+C#qtOv9k6w$I>?Q=UP^LI#rY5NB=*GatH2V?i@7qq<;7D{??m4+9U z;zNI+2Y&+jQ?e{Td^{udu~+<$yC1{-yq0UL~O=-*;!Y*f_zV?Q;b82wrfjqzBgvKe@pJ zjF)c|d(RCD-F)LOIo`D=$agGz8gcqv*waA=h92JR*wg4X7Ta68k;4Y@M3oO zpVZGFKkC8uU4Yja!#mHR4?9Qu2VO%A?-dT7*|kfzHHLqdgYV+4h~fR4!{>7u$N3r9 zI}{;uzc~#U)+q2@1pg^R<)Ph!WPZZN8?K+YhWv!(FVqVm>%Yx!pkIP(YdKHab*hFc zlpY($q-q)ko+TZ!(YNo3`g0`S{M2e<9}C(_Sx^fDPep6%k# z0acPu>4V-s!Csy6(v`VANKoPJ{b%>Q~rHetvA>sO0@B2_3yDZGhcpA?fWzay$xih$Y1GutkjhF z`%oNeIq*v#eB_Zwkeh;!tfrpA??TXUbWz6(L;Q{74s62 zi0Sp6k$INO%O)s+9?)G)b0dUAp6<(#{s}Pd+p>5&KQOw0v;9B$-PYTtNdJo*J-hlp z--)+=!Ti4UC#atfP#0kRh&!)`U!BanzSS8Yxbyn@Uz73ag!Fh{s>DE7~i?|NAI~afi7B~pd|YK=i*{OOF(?0d2)`W`PlkjvLQWBDOplD~Q>esQvTS{EXT@ zT@3l4hY6=X!1qD7eL3H01OGOj4Etf}_W1jeNXI^7tP)L4H1Xg7|w*5dWbQ#Q(Ds#J~Rp@!xZT_;PRjkQ=T;s4*+a;~f=*tpi#`2zi}tA8p+uQbkU<-oIh*Nr~<9q%O=;mCu2^S4d6#zI$$&SlJ*#!BlDD?HH~a^zFgv$o-YMtPr@ut z_~!c7bsfLJ(Y%fiOj{=jZdSP{lI>sdt)1j`-^=*7_aMxELOHJ%`gX5>D4#S(9>)SB z{Qh^7;Lp5D^NBkM+X3C5$i8ayEJ+UjP6+w3b^f)oo&&ok71I1$)>B5$ z5jr-XLVM?WI92op7|&{r&(?gF6Grco_?hQQ=qum0erxfe+!>{(a<8TkG1-$o%?bW) z*^lt|h&;h^sXu?W@Et6d`tWz_KE-lKL2{f=MnI3*E{6On1I7Q@!KJf%AMPFzS3_m!+d?fkSM$*SJiC;u=g6~nS$C=vh?0u19 zy}TFY4~}vEV!bHL*L|O2eY4O%H!b09+TPX?qx1w@gn>ZqAvgG6OebHDT|`=E7?i%X zSpN!!L%*o}`@3Yl&>xn4C)^VbgYfr>JS}$uLc4r~=~}xA$6=HVjBuVN=-z6L&nCoy z*uCL1)!qxUm~8l6_Y~d7iULN@lz8kLU?hP021(lwYgs1+ARfN`3k3v>dVraBA{} z`ct_A)x+o=S`K~B8u)bYmDEGbo(dVUBYWSfkP$}h{is5wnF$5OD1%|FL z!m@lFh!tB-QyAt;9w#5!KOVG$G=*Wl&SU2bPLcK;tlckqdM~KZIgkYs7OWl@Ydxyp zFuzpD2%gzV)po1rX3)>2DU0iGhsG+umj*o8%XPG27PganDUzO)f&n7ao*2Ym1ix-HxBS73zke8DOGqEFM; z7GRNoxnDx-_xuY4&aaaYax~LVS3mC{{n+@f(szP@5B)5uq^Ipd*X}6^Hl#Ts)R*4J zXL1GV-vU|24fj9U+^L|jo$i))%kO|Lm)C)f-9~x}Y^B~60YK_k(G+##W zQ9k@T(y^Xx?Z2g-BIp5(XLab`cQWkmW0cgNTuV}+-Zh`LL;L=MeILojy;ZCq#d zVCT22eflsCdi>B|LknbwgB)M|L79E;_{_c$h# z?@%n?)=B1D6SK2Tlg!r>%eTSFXXAJK9-M!w_$PlhA<^T{Qa_;MP#ez$Z8Ba77LTwW zMY~$!?DyLJ0`B(%`!OyZVzPW5aTstxVCe1o|CIA^2ls1dLP<7X4{q(_a;%@S_x4fl z3P--6u_MIeen7uxXz%sAez%XxxA*&9zB<5HF5>G2De~9aA6S1Bo{LhyX#FANxNns5 zcS?SnKaXB2@F?wR<8k?*;)suN;hWU|LP{^_d?jc*#PPvm=@+_aKnt(YHNAoNU7?TJ z1gECU`LPtmL++&4M1O%-%j2fh^pxxePoK^Gc+#7v=>xnYnVP@6ThRY}V zDahB${nBCP%bj1sa*!Y8w>06ezzF?aA_vVcX=Vie{ua^1_b~6Lr>6IFxptmuGJNE5 zPYBTfJ)75CKbuNmv_kRP@7p-?rutqsE<1N<<1WaxjOs1r-N@-EA5VC`=N^&Q))j*5 zr628@UeER6?-u#GroWl%1^UF3n*KxKPuTGm()1by(0(|VGqZ=$SZ`o8Pki9Pe244d z)(O_L$u&>gi+`!KQ}B;pE%lwqMs!c(Fyg0C>;I_M|3cQxVnU;lCwUcB=65g+KgMVAUX&{J zhV($|Ej#UnUYg*t`dhJvVk!Pok(Z1b{$ z`WY+tx`MXXL0}V~l#9omS`V)OdbL~6VL9TTQqyyMVlFlPy$RTb>DlUW;f(2%wx^e) z99wUwU`OzCRzLCjFuANfEKKY_3X`6U{^phFPxhfx)6eF0Yxoh!mGor4Gd2Cr1lF@n z&ZzxXJ+S--`&b~5cdahzh(X)ZnyrW;_%^>Cu_e?y7FkdsaC$}4f8aN;om+; z@{RTi9QnB)v7a}2qV_m>I~Z{Fb_~A*IYYlgdIKD{i{D*uPbAVrZz}Y5#}w(!KZf3H z{_pxhbitwi$h_cB&2D~oy?r2&COT80x9g@zZ&#n7-p~@Cf4yy;GQC|eMS45?7DYeyer$7nURAx43@~wln)>{X$tk zrSJDH)phrz_ZM)BW&OqII-zI#OYrCIbhN?V4>kX7{W1JUhO#_&|Im;~6P>B>58t5g zCRXc@Z2n<#{Vx5|m+)Oioj)FHJd6qB|D3&dQ|YjGnYS#Y{Q>x^$&9x@0{KEaypQQT zpZ31T)n8bSuBU)WdQN0s@_x`e#`pmJnbm`h3zFUeF6Vct|M$53AER78S0ls4vhhe+ zdo5cZctyqbI_YuhiguRIzqFOZg^6jCuK$8l|G%sMS-M>&_SNjo^*8xrlvl|={Ut4( z9m_vK<&l3H5hrjAhV6dx_Vf$dC=UCz=uxO_@D?jpVClPdEpXAUWc8P;EY) zopu`YY4+vo`gxb{@5rFwO=cb7k&62LCs_}F zHCxAwpG(Nr{`y(e2YpZZx5((fvJ*l0{(N};k_K78Ysug*txsXlMNhW& zWR9o%do_PI$CsVoteSs@=6@gOH@?I5KFb$7Pm!(tSLM53@-=a>;eAh7_hWwT9I&uW z?={=`v%4kVB-a3sbC-jLsBZeO+^>E2g@`p5cjwfk=-oPBM6PxFk@^9`S+1#-*>iA;D8y`4)! zd_PU-Egj+89oWv>$M-{F|9I^E%y7T1QP$N%IUGCY_wsj1KAcB1y9OyM-~6_(`&Irf z-G|$KMD%m1_`l!+Vw9fXO!c307>wT2AQjB`4Q34T1Z%ws{IdI`g0(Xkw)d)0KKL-d z8m!d)Up*IO=R@rrlb@0D{i}seuz08V$E&5BU~xagsYI3;uya!WA_n^lL0_9M_UG=_c=VQ{_7&w@6oCx1xv zdd8x5N+o`7=77fm%%7;&8-0_++j|a4I*o=HKQHnAdz<-(f04jvG}Z7A>lch){~{si z=Ow(ZNzP&W`u>Zr6TIu1gue9y{zX#n{(1?oTOjM(*f)e-O(&a$p0c&FUR_w1;qtRJ zDGvPyIPiQu503LnXb*^`;GMQ_p5(7J&)cM)qjWhRZsW@EJ*36^nU=MyD1MmZ z!DnebjBvc26Ha=6&v2YioM**j=br8T=+T_SqaWjXUna1Bzw&*q@SUppgwUnKroiwA zr9B1pl5XRkBy)#!v0J-$z}ItZcJH9I7k^Oc4IL1=c0OMHDB2T_JSLetqO0Xc<%%69 z`Se4SZs+|YJkgWAM{acdLD6f3H^6)ZdQJ}hiRbNLf}W^d_X%FxHVN&$l&iEI+jl9h z(sr!hr3l-%wtL$@3-o+&&?tIKC0@?u!C!yoi6@=_KtZklu%x%-q+LKi2dSzezqeEL zDCX^rZeTL}J_p*NjW>cukuwE@qQ^bQ)9?v8&vJ^SyYFofE1{o3d0%nLH9v0le1?9n z%HJh)!t=U!aB=iLhv?nEQ|+uz>?jrH zQo6gfelOSMN~hiC;PsO^#j2FHtE;b`*))A zTq3WFcZl$;-J1QIopQOPTH`;be0J5G%S%Xu=whbaGty*Gic>!|L>?|q9l+mY?$*^XmJS6*Vr$%`c~lB~pu z_t;r%#kQg(B$1w_XIZiKCs}@ZKZu+KNZ9Ozt)Ye332_L}gch1W0l84t77|*PKnoZO zg;ENpC6Hf%==c53nRDNLSCTD5`zihZnZ%kmGiT16IdkUBne`H$S2u$29LeI2bcjUc z5;?zxdXeKnIF=W8uEbx#L%I@?%Zw%t!m%9v&K3DXdJ>W4=6p{OPR>`XLaN{)9f?Sl z`R;HKj&c)sWPA+9v&*H<6}d=6<-XNO4I<&<=4*C^^z&pz+6@xC2P)_dE2{|dct$9~gEJ=X$YIPGU@ z?{oZ8M>|RHE$etNgihiCKPCk@RbKiRssb56T(gB`kbQfUl;1yNgZ#CVWuN2ruE)7XJe2 z=k7u|e+u}ZoDp8a(vK6ahqr54k4n?u6Yc_Y(y{d->Cc3x(|)Ys)*4CIe$05$BZ~|? z!_v9_1HB2VO#_G6|?Kb3)lVbRBo z6?o~F(vO)=KKSQk5te>PD1m4H(|%03>+TuqZk-N5rX$?gy&qu{baDrTPQAaKE28uW z9+uc@_2XMiEc%&L!{DhzTyi~ohv20jcSP>9H?Z}f7w|9eeaubs-8?)<0X%C9J#hoM zDDe1R;sO3nx2%f@(DgM`7{3wk*=F!7;R;;tmgh~r z+%+2R3sx>?K@J5WeK>{W!IINIyB4(R*_cPO>6nI2O>K>ktX&w-<#NE?f`){n|T) zU&POfX69PE^f8moykln8P2O$8=={&I-jx#8`TvNBnP2aOk#3O>(y4r>USAqmAJXpv zg!xUlg7p)7PCm$g(#7`?>jL=5_bbpPl>dE4r9Nu!={ZWhAD?}3fsG`kg}k*zf6d-H7FW z5~M0#T|e>fuZ_Vk{Y2;8%wGzT^WWX1P{A?&87L6F@1W=EgYy%GJ4`q$SDMcJgNW}s zC(EiMK zD)hctB~WwyWnU;?G>}j4$LM?ID(91L*CnO`*#W7?WZLz$XVtC?1ixKRAwR!}{MkN{ z+u;1@{{-8sPw1hZqGP@rnJ(E1=JS^${uxn6M&1bDb;!1ljo11Ac<)<+sk{=0U6(Qf_uQh!|^+f{K2h-!m z3LxA$W;{^11X8IgbUwg*Y+|+d6;A2qx*hQa??16#!Fs$F_0aNl{ma)omOg(xviLH{ zL#OSi@In1Q9O!qd|FxX(IzHQTvg5$v0ABJn7i`}fg)Uw18L6}3i^bP_Wu#*hqS&tF z5Al5L*}{ABjAuIGn0^p4q3sC0YT6s`M~MFrIssg+O8o3Y$Y8?a>w0OfN;+WnA(ISv z@!1|!iTkTO6Op~wrmtII!;&wzZkY{R{OeZOuq}UGy$SEH+F*R*MHTSAjmTN9e%Q!c zu1wZz{B^}U-)`ddo}hkLMBnvR`$^|}IlB&>zdv}QK#ntZoib=Qnf~;cAq zqq06ky|V7hF-gQ<@;QO?4JuzF+OFFKMBk0j_q>Dcx)&h9cD;Lob|oCkSzFMq-YJ%# z_X)Ep`A$yG-ZRXm^c~$6;a6)&4i4FNu;?6<=_xYLwN!w<2~hI zx@Y3`J*M1rD+gBZ=cZdZ=u`Yw4(z(1@+UW4tI?n}oqb>ZRNrR?hAylr#T5po~S0{lTt>X5TIC>aUYtb41eX zu^D%}$hdBU!bkqEPZoffi;=dY5S9#O^sP-}K z3Q|D%qXY-rw|s(rNjR4K`2haxe{sL^_20u*ULO{D)phlpZQrc+ues9hYIo)4iahxJ zbFRoq=3!~qGqszJ&*SO6186tDy{;C1hsQCWzu+rHD#+LGJ#+eT>|H^=+J2(%ykvP9 zA1?Lhz;~7B-XP!odRsl=^F{c7w&O%DB*#I%vE}|N`LrOwCmsK@ z3vE5@yO7z%R$f?nxZE<)Xa4(b_PsNG_rA}bhs#<0*LO_bZ`OP9eaF5l`f}N4jo)|d zpKSPq4a;|a)Gp~e_I;cFv`znK8-Buu<=sHFfBNh>5Z!<6J0|;mdcLCX*pF;}(HDKk zWdA?6Oyscd*pF?x*j0VUPi*==8~&*cx7hG08#%a%&`iC>C;{3Y=p|G(nn5*^#C@7;1< zqVF+zr?T(S-$?wim&JdNeaG%d1;L$6rL0e9s^?31y{w;R?7CjYm3@#*O4h+L)=tSB z!sHV!draucRLaC6draugRAMkU=~AyuC0DTtFZIq;%DQ6qnD8Z2In$;K-!hdmY*_f3 zskHs;nD9MQ$<d&GuifhBwF2^)riW8j~*VJHm~e%6L#HhjW{X?2mF&)P7xB*V|!FchZ= zzhJ{An70QON_gzP<^s$qu|^xIHhMP^5xX!E>5=(lwdCXYbxYv<6YU9V`ndB!1CWkX z%6Gi3a?bZ{QM<+w6X8%IxRpy=Rtt+kZ+M5uW_m z2|w;U$RP61v-_{jcK@~2?AHYIzy4Q+`8S*V519Ok$R1;7Cn9^zzC$qI=R)nko!Pi^ zkIk2s{hLUKS+Nb~%f3`t9(yqEx!ZjEWFH6ZM#&E5s|e94z|!{y+EM3~M-8|+f6%~ow01#o{f+IGn|si-FY!DPlq)-*qg{dj zQJ(Z?eJ@7mlRU-~EE==2@Pyd5L`UB7o3lsC$;^2^xe zP&skCkErpKvkOD*eFe(V?}`NB-uBDF_FZk@*5PkqyZmhkjyrR4NA~}M_=-dFU1i{E zh_0ZWYeR6H`ssH?g81GVl7}q@?oy&Fz}FsII>c{zK% zf6UFY_M)B>)b9n^cl2`hdw9f8$W}26z)#TyI)4hTf4_+LFSH)ng)+|PszY{YrPM;< z+JWCg)R6Ugo`4nw&l8Z-JWoJQrrp&>PjEe(F5${T0{6j@6?DnONY}gL&S3+W#=8%c zsI*&Y>XCL=h+cGGVfXD_6OKFg8+hhtyTqN>nsC~kX6DUlx7xhulyGl!Xlb!rk=MHo z9P>%|kj;0#8E?~WmDy)cxHp9M;d6dIS}<|vPMfdFVZTba*P9bC33t1*$b?-JA9n@} z9LtmbCkroWcey>ca1C$?eY9laj_7s5OSlWa$dBdbWK6={;?N>weP|)ZoqbF;`vwwj zH(|oFMHB7@8OH$6aNOBr(urTfDHBe(SBc%?u9NW+@NCDpbE^+8;a6Drwf5Y>mFC<5 z>7m@|`7Gj>aHEC4-0|=$;a=fX8s9bT6n7d-I^n5t^qf}0T_bjnTOT?{a*Yp94WQ?= z5^k+InUrwr9BMeyv(%(-^5G@C(S#F_Pg;@*w?^y}z*Bzpd=}}E@LCJM(!7kFa95e{ z01>~8`*kLr@Di@I@YTkSOF(|9*9gyk5_hO2q`f38h9UFQQrB}@3{!6P`^pT{o`{>T z>q`EGCVVmeN|yD^aKaw}eLjCFY;d_s@ikt3+H33Ydg||(T3g+)PQSOo_#dS| zdN1Sen>X|JaDE++NA?jy_8Te3>5`~vr$h<4g86!j$eG@w)N|cTCuurf4xx@7!ld8B z_^jW_?Q1~>~JJ@|SEsu1xP>xfAN9BX_F6LzVtw`7J zL}aO?;PiX**_DDnyOL7nI4qz1)q3iA50!`D_Z%|;yXklNe(`p6TB46qA`9$Ra7hK=JJRq<6dZ7TgV7+b%)awuSk)Bh$)9{J*6#K&;?4v)}M}M%7 zsHbL|enI=A694|U+DFv0RGF$@{ryZDad7&+EbWM}opRuVr?*q8F`yAY$Ft0^=-XVc zj5}(da5Mz!eECaQD)h3ok92%%0+1R1-3l51VZYO9I>%p@GtqoJH+ShN!qW~raiQ3f z4Cm%nnRwQZvHD$L!hJum?vk4;=OGy1QjlNHMR44vC6c>C&P5O(Ela)Ur}z4Fzn}Kc zw*&Y&UIB&qzVJt15StW1!@nQGG}iT_guBw%2}1z(kgoM7pD!k#?RPhV=ef8Zp>VqY zO*`;|fpT~clBE*QbWBi!>vGQr@-tts{)B%RwCFiUhW{O5onMSR0i0`dX50-~~ltC?r`5y_i zFX{MhAV0&mvA+06H_~D}Uw*Y+TJQK;IVGXZ`;+ z*bV@CBG`U``mYPo&+`P7M|Q2u+#d*C0lYsTUZm5V4PQsd7t05pFXudiNzbp> zc}CB3-7SHXgnNr=C+7c`Ul-<=^|L>>`KQ|Tx+=3CmvEcRx(D;^{!Kn#+nH0H@3Q%Hoo<)WFND7&RF14G{E-jO zc{SG$xiUidb3^!P0mPk$e0cIR;qEZw0O8*{E`H9m2)EtzbK(c727g%yp5sZ}c@4n< z-(c72b{flr_;0teu3|?^fcLZx?7Aq6Mpu%^4MYFZ?@}nx61dr0ly)HAJa$hr`5v0 z!miWZZq|c{|1%-_W!>vG1JCqthSFtS>t;WFZJ;0NdMNYV5XvWf-|6Q&9HLXwxBKbu z7&l$kLy7AOHmr^4yGGr|3PV|P9#`DjNP`zIq|@`IWod-R1-#_0K( z#)!yO?f|Jpd3HXg?Lz)a4Nda!{7N}5)##G(!siz2vwr&S8_Vks#4G=K?}+%cKKdRD z!*rA8`x47{Q5(+hGvBEh{yX?y4ZP%YnD3M?jrZsIwp|kuYE#g&UfNslo9eqkjS*^- zyj*hKgPcwjb9W2!+-E#v*E62w^rK&De8N~2?1Qr2toL%YeRZEi+h5<|$*#9}A&$hW z@1PL>p#uC&C4OpE$ZO!c%}i1J{=Sod`}edGeE11Y>E--{AS~zRf^zJ6L%ZMO-z&EG z!u>?+6TBZZy+X=jd6T&x6x3rAz5B)f=mqeS?q4E~KIK5aSE%|?@B1jEJ>wyUQaJC<6KhO4C`HEc3>f=72@%-E{9CAPY>g#P~L2OqnQSEvIiF0sf}$EXrzOx1_8m60Pi~tjf~@O+ zH9`G_!B4LZ*?1x<`c z_DeOLe1Umr?10Je?{svrN0N{D-ywac;0Ni0h&kDPU=aP3=!1$wCg`l~6Wy(R)czRk z4#F$7+h{Of4GewUqDcQEOl7y(0hntF~Pd_#PKsx+t$_ zs(_#E49{Qxr5uBmW?XpH2N2Im3S46;YaP%tdrZ*4)dle!k$At}t$DZ$L4U4Y=+03g z!sXs3>3%(A8tM4=OKiQeSv$LkSA+1HFMO|_cs>EsO79IenpHLEF*L$(KyOXP4j2yTL$w2P z4_Lc>&r(BwHsu*Vs84DCla3Fdi5t=L==k0mIj#LG`0m&HK(BxQaG20}zHl7a`J>N& zoN+I;cPtH_=lFjAui2%XcOUuQjPQAmY^BE}C%eGXxk&UZ@qHywFOIjI5x)WdD3Qw7 z)8#)I%1=u*oL}t^^1bR>k#&H<{*rau;CKgi<@b?}6H55?EaHRf@n#-n<&yO{9B9v= zy`lXrjbg{+_a^}0_fu<+lg?Au{P)oL$QkHtn1Igr2kBfQd{Mq?y~5)m*Q=?S60TH? z^9rgQ+9$F;BZ!_Mlfc9MvluISh<0{6Z3D>UG9?_vh=CaL=*lB4?QsolWR3 ztYRXHA8^_Zl&2zeghpq0O#I{MV2vgh@rwQ0dO&22^fQOPa}YPH*}z+E(3tU;Sre}G zn@QCdO|0t|lY+-l)%~^~X#Zz_;CLxifFAIKu35}$X9!|KK4|@SRGMV7uKrRmpO_s^ zv#{S80^2BI`mUPVeHr^s@cw0Tf6UmwXTU=aDjubebTPgOKIi*7e;q<3^=}h=6W@yf z+z|e;zck^WiMRH^5P;F7=G_ewKZpR_5Y#zaun9in=b&>fImr^malbYgF5hM20bdY* zpN&U31@R;jj@r5)ejQ`Q9xsSrB=NMbNKbiI!sP2K@iVhb+OY3IiO(#v_v$ui*xt7T zJYB}x-+gy$*xt|EsbPC>Z-s{CJ|EkQ2l6Bqd>^OXSv74h1CI=+gD3wL{ah2U_uF^&v&Zax<%wAJ2~ix?d2qbDsvCzWsY#%HzD?1mc5s?kR=? zcJ9+>upeg9Q(`FfCl93N@l(~i`0 zE1ajH%Gf{Q$ccG|eP@8}N2mAMiSI;!e!ZtoIG_gHLBqe?veycKkD2VIT^}L8?7er@ z(`;{!zqw^{?~(X__^~{_`aJ_}pI|U%s1SL{7~*GKWN{G}m2BkDAKUS{wH&v%hOx^9_0c9Wr-?NtRx(l>V+ z=nK&u{LaTWQJ~&i$-c&*M>~oEl08=hCMdrxEU#bSC=YBu%6;2UKVTvc&HCtX^nUwO zb{uB{w{BntcKON<@(XPWKg*{Ltl8Emi0IllwnWedu^=6OrErGK1xw$U?I50Z}WSi0bi=Ji^r9~_nPY44c+ zXz{Y%)5p~D48cwle9l`(+6-E!Vv+cX7*j>==#O~(i@8CA_*Pu8%6O%*Tez)B zek6CxJSk`I?dyD={fOhc+6T$=<{3n+A2n}ziwT0iAQ+CNlWskao3;0vljkLv>|~3i zzw5m%%5fPS)962C$9?YGjK2Kx%Z!Kja`{dCtfeZNxg6!KdH3dQ9|%wyWA5+TN;H z=5HM`_0j&J^Y6a%WG#VkrbX}im0@2hoxoZv^ z_}ne8HGX4M3eKIne9j@#KIo+;P=uGS{ukIIwQC;O<- z-RRsR@S5*Ip(ktiF?2sHoWIf8b=rKS_lgj`i1PO@vX4sr^}A6@SEI94@Y?&Ex-X{f zu5kK}b==ut;PqUU|GlsY>O;CVhUg+6$JVdb;MI1_+&N;}EBl~l;sDXl6{M`PwH@$LDct0b;i#;EPAQ6O!{~0TsfaZ{omkO zjXr9~!!{zMd31S?-olyCKSNHkrVHMOlXRJYW%o#Ws#5xW6B60q8!?{H zWh*^;e1zb1LBU`L>4gksg)fjQuV|6XF=}*Lx8HaK3&kk#yBteJPR4Mx6Jj z%i8_!RG-yncD~sJ=8HZPJ(8;zz1A2JLy+xtZGgT+DO(HkSa@MQM*0$^{EKl1osp2) zK94~sJ7D!uNRR3F<+8_Yxy~Aax9=8)=g<0m7%j&iKeU{FTaMLxr1QD}{Ysbex6!_H zp?t|aVD&@4Cv;@aQa{*vvpkaoxpTK&*lW$^Y5S^aS5+3AOHKh}B2ne>MCQ-7V} zm?6Wg8%aNw_Wv*I$B;+5j6Em9?=w;|a&I-!WA3G?9QXO>f&{M76&1?#Ph$Tqc0`)X(QehqRSrg=sDTD$I8!pUuF*xNBLoXl^@ktdTujy zD6BU`-=7Zu_KDsl z^_+gf+JnA*qT}Zh1LkB`3BSkF57Y-f-)uioe){y8-6FKZJ%UHueT@BJ>zC)h^e0`n z7}+57Kq<6<@+Ck~`^@X(yf8Xti-&$WN|Np!7gzdk|-%O3jOvzPW_%0UXS9pvBguzr5IB<0{+f&m9m&;F zA+?-}LpJR$a9 zohR}2Pn&RK3Au>=T`l!bxceo1OwzOVykI++O0BEwH01*BWG^=04#yL6-2c98*xr0l z=+=3>>YYaW?wNjnIGYlD!E=us5A);TJ_EpgCy->yWs7m$g8uwhi$2xwMkJ!5@8gcV zGn`$s)Z|Z;Za020h-Oumfc`r8*`v1J_Itd#52No3upFtUj8`fb`u?)+KPbPl$DTEO z)^)w?Lgq#}mBuT$H(yxKYYl*3Ph=x~T2J!(v*1gjbhUxgdp?P%EP#!%_xDW1|0_-Y z)FF}kR=YmlXZOSOUBlF&^9-EMm$HXU5aWX2)$>H0C(tp>_R)1#oj>S2(&x{kmOo*_-8EirmA3kKeJRqn=B`SN)cHjo4RePwMQn{Zj3f?4u$N zBX+7!J^OAz=Ixy&CZB%4H2Z4dck->;PSOvW&`wNA6l?rdCSQfMr~B?_Zv~(1`x?4W zmM)c(bcxb-ldkL3y1x;3+Dtt8=P|$C&r7;?UAWpL`|l{|J8i5-4h)K$Q;p!~Z^4Iq zfjZ9zt4H2U?FhY}G9K>xN&93UEtE60`^Xn^By10}K9?X~$HQH zY{Ac1U+tlE?B7R6fR|yqICewmrw$1Lxw$7qenA9dTIWc7-ytgx_B)Hp=gdKg56?$* ze?|9`#*-RQi-^26q{>v4H}FGzZ*e>FO9Hu1_Q?T_>AzKr&ZGu1=wiG*!mhRF}r zvr1=j)~gH3i#zw4u+1S-|5M!eo}mP9;@GhSG;lDDmUfMk#e(a7&sk2Mr^;|v0Ks;v6bdW&YtVd z)r&kN;+sr-)_xB{_i6P#mvTE_VZWlw-YW$rVs;--)T-m>&kEf9HL}h&@-_);JN4NC zdZa<(+u^YXea8A}*bS!r0{K|9!gD|JB*MwK6dc4)6~^>aCS3>)rbG1^dz0-@@gV-d zm0SKM+hiP_U%SA>_hpRdjI0trtUuE+xo5!ISi4+*Lh0 z-`>yE?|A$A@?Poh+0Z&>-03oe=ze(lBB4w1>Aq>0PV(af7}~X&wFVzsC7+(_(S6;V z-9PZ-YXnaBJ^l6RL!L8ZT&$8F=QXNe6n(vOc5INS-Vij zLyGmCXjmN=)$U6a6^{$A@9a)IenMfwtvxLJ4;2!0)LzkXE$zzp7!!C8oNmP0LAo!j z_Xf^9zWyXMzRG=6zr922fw~^8?Ue8DNka$iMM}1wM^L)L>%#O1! zc-nm(Yd7gSiS7?Jp>U3bjjr9-ISu~F?CUhy`s%#!jCx1)KuYSxeTxfDv#zb}cqaX$ z=bXarLHspFlcCaZv}dT-{PC^QM4R>U7sfgN#CCrd&J+0cx>(?SeOlw0h-|gUr_N{n zc|}U#DR&F-GgmM370NBgx%|E5FBw3sui9OS(odLpwOeuzKVj^0y??Fu5!7x_`&!?t z$ldvff!Fuj%kMYnFu#1>#8;@EepKdR<+k7HdfNPRkDCmARgzD~Q_fci`Wfrz>IGlg zT`c2ZwZL<^2UYji8~dse0Y^E!3jPS00M`V+0>0`Qp3|ZT?+|s0x3OL4+X(AEDa&WN z&Kvu7vlQTga1-y2^(N@>d?t&?-6Hi1+S@!CruPMQY!GFXu6-U*85H zxCzCXaw;WXJEBP{>wy~;{yny0;A4N&^(-w<-(}}}E2s+O!qO+zC^+x(J&bRk& zmtiQEIUHc^J1XBNOt_yK9R{Ahqw+n#zGLzozzWMh(mRUuMC8OM%hT_|CGW3A2=S$| zznFYb2$+9yiHVP!S3$rhYd5RhX@4Vsf^tCm%zWV7O9d}4tH34iUS$0FOIbSGQTmIH z*X0kIXvaS{_>hFNcHKkQPjnuydcV=R!2qU?irw34&qZb}Uvz(o^2Yj8&kn=qyq&(* zukX0Mw{=v`@viN&^GiGa=(=0xVM$-?R>=LBV?YAO^)uQbx^L09K;o6&?4yPKUccww zXwQqp9r-S!&*wvuepQLX97^h#jBEPcv!s4xeX_?zA5zZ}kM1`mD{rNEm~*zMs@T2g0?Epo z{dh_;^AS_B@=5vYI(ew&!&m$(< zng3=}u#dNVw!}}=KeS)v`-!xh$|Kuv0Z@#QOX>E`$gtq|$Gwdb_UQ`i1FL7Kw~aoq zzRGzz@-Kz*xN44 zK|U-&4Ee+v4%>^IZZz5*>(qB602j0mDaS?+1CZr@{+H}eYR5&5T&aFxJG0;DdjwY#(#z=;%g;QZdS?1r=^3qu&TASS&)|<-(Ni{?Mr)G4%db1WyVP~A?1;D_fJr-=K}T8{U_DytjEu&XF0gSah(}aPSVaF7^Z8CGZy;8 z?nfk|AC~Z9;~D!Ork;7f1I3M2*!X88+2QpO-d|#nety0#_gi#bP|p)gWRK~1u69P+ zJ?4y{Jl&VjdrRT+lC462+&N|_^2|D|Xm%z4?&9>Aj(~HPcK)IR8Za=$bC&={jk$Rn|@7&V2?C z&-~0H>cQ_RPF8 zQGAK>bA(wgXP8a!S#Vf>78lP&FDd`lRtx5!F z%srAZW52tXYXyODc{@$|y6)5Gs;Mo&N0}x$IeRXU@N_vF@7rruud$!uf78#X|Mh*S z3OimT7d>F|kJxdcVzUXFbwW~<-1TY`k-J9d9+7Tk-W4L=X9V29^XL58j0K%`eY@g- z2|Bu7sOS62?fg#lPh&}`(KCA9rhL8ROGHF}>;5!*zp00%zg)*h0f&B&guVfZ7_(m+ zmV=nIkM;-EzlkEbk4QPAi#uY6m$#Upxo1Y!bDaQe_0J6rq1hk<}8exKDg7KWFz#Mr66myu&kUG(GkGsMB04@I&AWIoG^O!sV7f zbHFV5QnmQYZ9L(r$5lQ@)=9oZMD&UF8{MZ*M1+7Q6!L3;w!=4i6vA)<;pCA81_9-i zVLfj+vdcvK`$UQ86#{4T>3XtnH-aC`ul>E<%Byc@A|FV{yD`GYWaZx*y%p^DCsAjg z@0F%FN6$;fo%b4etzROt%mAEuAK&&vRvJ$GkJ=yF&k_;Or0c!#@>iRnnaA>16Zk_E z?bfH=6zqT57D9o3)SHg|wFv%k{4??oAJubqG~f?hvxvJ>?wO!D-$FUydZ^Ju;3r-F zKAWt!vD}~>>AA~hyM8l-JVu^E_zk^A16X;o>4dMszw&kX1E+eP^9vM}UnjcJBxAk3 z&a^|WzDn8y^`sU`E}m@w%=`i2zUfj}J7SFID3=il+*&^9PHxl;z3`HO|Ydd@}VmRjWRpiR{!cM2TigYU%9 z9w1b5C%Gi{0sY)9TZKP!*NK1UP2!g|7(cnNl3-49kthB}$sc#FG2!64M+4~9^$WJ& zc<-d>x&L6kbtd2Qro3Q$orynT;)$07CuOQ#`56>1Fz@qgLDfYCaUkVW6J6M zE$it7*6F#fLOsEH(q}()G9&3Z4(p&Qw4{j~VR z_gd6L@g}0w1~@mh5CRJ~B4&?MZpW>@MicE&Uz>hqed^g0z|++bZl4z6+ak%g*2=T) z|8ZXq%f+UD6M}e%A-PKMFifZKxa#{KBUKVlDhau^f!Y)F-eA0f?|@eY_|1C09|dZC z)gCNwviZ&vU&qS|JC5o)q>cyVC+o}iOXye+1V{UXZAN!99JZ^vGIFlKf6rv0l$VHs zk#NZkb{=HoD>h2{{0+hn|NEHH1tvl5(>~iyIzEQi|I?A>Lf=fmLwn>ZV9;^93H7O9 z*z%Qf#G38!@CR-&)_1-QKW<*-g}y|7ruR+94>+=q_;yIp(eJLX{$_vILn7RcUh%09 zPvK{>X}*c@@y!4saGYlve0H1~v#-0tfcfv$sJ+epUZ8hD56|xh&yU;{>_;I!`|`e7 z@J}S~>G*S|U#T7%vGP5VmVA`2`DlM%&Ke|M=T)s`ouiE5dj2r{WJCmq>0}|}IWQ;n z{Q<<2GbSDqpTs*UbcBcKVVo5`sPd4Td(hwy>NnAgr%e1p>Bg6*L*om39Q7{!xYHo` z>^^h$207oR`_|bT<=k5$da)^x^2iqO=i}8T8s{s7kJ+1ycsshU#{T7?gdzCUu&USm z_vIe=p($VYMUvHaJ<-aI&cEl+6axE}Nc*e(SUxD_>pMoYQz=68?K-X6smX>~1DL!| z>~qy~345MU=^{VD&QZ0m*&j(@-&QGy`i>OLpR>qB`1cEx-p5Xw`edJwdgbXCy8V4) zk%QxQ9V!imJs}xa6Zn5x8tFne;p70=>WX8zwSd)FMFVO$ok|v z!^wq9@K@xRBr%?ROD+_7I9+~nBDrt|v)guzAS@M&o3E+LyC83hvS*6REtCF7{r3rg z=9WqSWcaTEl3Ql`+q(sx_GBiK&rkYz5{BG99`bYkPG8%Fcu~C3TO5B`e}$xU<6fw#k2^&5tmhuA5QbzZ*w>_#Wbitk3cLyTC>LY4UU7 z5sqQHVE&5p#?M~?!yu{oP%k>_RaD8?myFu6nz7O!S%RJq+ac33r!n$Xqi0m!sP{hf z<6n=CC$}UmxlqQXAbi6=jt}4P%`(7$z5ss{@SjBch4CjMpBg>+c*H-)MmWAxJ{Io8 z=!MTl%szqR{B)Gn_dwT>_1Vs>C*|>!D{sUji1R)lHgu>Q=zTNF1D)D`vQ!m*q9g4Vf%r?}Z%;e25+mu5+?r9cL&%6p^I0dsUA%K`ywDLi(zh zo|1D^3^(Cl9Cd}~e_x>(bPglD*qv^C-EVviD-V~OEB!razZiP$IIH=`e0RQ5@@v1K zzUvI0YX$LOt(j*X#KMW{W5N$3p76t-6=DnbGwhqHKK!5>2p z;tJ?tXNO~XoEHV{dc%kHlHMVnv!BS|bUmN##d%FKA@dn6kH?}5%CU5xv7APO-q~;0 zM|2&C`Aq+Rx1@9aZ0Or;!=S4QKQp$RiRz*KllCfkRblJr&uc7x9Y@W)Bk zcmIU->bFSHQGKfCm9)PT?lBM^hD+J~jCSDjfa5dPPsgHCPfM5TAAM(O#KtTBG|s1x zFRE`@-do2jPnLE3bqT^983%4Wz5e3G{XdXMwDO61e^7xIY`ru_2PUmypB(|5oUSSxZ7HvJxcW6}9$eN@jma2aw~FW-VcaD=BzwX&4~hkOEug5k(t(y#mQoX?RS-B(L( zHelwwF-{x%`+>)8KIL;FI?Di7SUo^}@@$aLmJaQY)H@z>3|apfA;-tB@9r;-Omn`9 zvUHtRcTJsCEb8nU(MdL-3ad;e}29J1OiJ$l_@KS^{FQZ9Q6U`{!bJj;sffNI149*VB`$ zsm1f{JJMo;PC5D~UHJTGmI4Bo>lMEk_=tyknW&Sy<-Mm@+IKPicT=q1Q~4;T@WAPQ zUS_w1iH~@?&Xs)|rN`%MO5mtJC~t8`?&+yrp!?Wo=Ev*__#yiaN@o-5#SY2+f#7+e z<(7VqCv?w<=i3>2UN%R;fb;o;-^`MX$vvi5R?GY`%%{vE=8}Ab?>blNsrO@>ASa|t z&+jTf#_(%3v7mpT9!5SapO-*hjC`z;^u@7x{(2!}*pJw*+Ru_TQw^ZcXAYup`hC@6 z3r{=B$OG`3dfIhxr8|32^67i#^Y52-rhX@VuUsX9qUEUm(e^0cEcsP#`u0kAzFjZZ z@u1k2-?dTRMe*OUK|EdSHS%5f;Ql1-Fy&+F5lN35Ee2g__otM<)bAetea8AKpDG7j zFV4GDS0!i402+$ln4Ag5!7(4@nd#)4xKno}XA0jHuhvh`KadWNGlh7aVLc~6y~rk} zUbzx}h26iW{9J|bT^|0xDg7KTNQa5UAL?HX!@o<<88_SWg&YU-4)nH)V$%KhW-D*W zHmUy(D{oq#L`)Qo+ELBc&Pe0^QI;E~r>oNHD-=pT&98*O!RbBzG4L~`!C9Wb3wPD7 zQaUDTPs#&xD1UUk)$X9+yw#7p})d806K@D4B&w10%CaF#E zsULf+z9OD{|0s8ovqJ3@vF)@Fnc#>YS^V=T~IE99O2JJ^jJDHB-0Og_lH74kkzfF97U(FxCT5Y_&_pa~y z>A42dXXGLvhs3+X)}tMXOs5^?!XU{$BI8FI2IR|#^T)Y@`klIKhYX`gUt$Pg{zyUo z|2xY3tQWI(Wn|#yJR>OA%}NJgDf&AE9q`J#FBKVs-dP--Yxu!(NPjzg4?f2$`a1sa zI1d51o&oWBp1{LT_7D1Z+4+gW-DS&H{igE|eYZi+Gm`Fr8`gUaYOfVrKI{3d5l^6R ztCox$PhCXmJCvGkhdW349>-}|=G1pP^*ou**VtYj(y5Q=2hTZ~c;$y3hlap*184cT z+{(f7c4USdS!XG?0c=gE6WDB4M$&%bD28J7KfAH472Azsgas@ziV znSAy>n~qC*4=rcu(|YmV3GwW(hJc;}TW;HT`C=r(EpIWNQ$B3{&DK9`{RZ)K_FS{_ zJ8ZWV+DrfcvpdCl!1!<9HP`XcA6I4yA60Lt{$aWqSF|536gbA4@qe6nGyabgPn9wr zelHMz9EFWL{x7oSqEO12-jAx-An`o+RWSZrI-57ze89_kG+TIGAJX-&AtbUs8c+MI zVEniE7+)}NxAEi$$NwIyf2fzJ@AMoJ@pBwey--nw0G!sB{hoG0uwAGJ)$Xsbc-em# zuj?s=cHi?GHD3EcYG5GU)jQw~^mwVZHgBk7V4v4>Al<*Wvu6lsoZb7)Gq=0fM$ny*B`)oj?gxMc@3zcfOWB`q0<^<~v)y_^;Xb&HLbe|JMA9 z+9UO^fAPLojXLik4X1PKy-Y`Yce>we?I9(p4p2Ch%6JDldOA}B>9+OVUeC~J(i{4F z+WS*ooBR8FfFm`SNw;}>vRc`CFVo+;ye+-FtEcU<{>$3tGykao!-#Cp3bzl zXK?S{bpO(eQ{JYIOmAl@>veSXcBZ?)ffU)|^`{30`@4}Z)$OHCIfLElgT3iikk^@A z-k3@EXO_2hbqw@(EFbFW-@m-QV_-QD4eagd?@A8z^mJyHgMrKY2Qvf9J3IFDr~0$Y zQ=OeXt;Rozq ziw81OUd#5L?sSU>EQ9IanXJ|xlx5^1vwY7$e>%OqJ9VI=9c=mD$4 zHTwKO;r$&cN*U#*-n%|^a929rc3rx=ePG|_gRS_TZnG6-LS`FEX-{?TU>k1)gNW3y zVSRu5AltwO19G}4HIPyuk!i-P&;Q)r)3d7!tU?~NZAWVd&}^aPZtHIC>F-Catki64 zyS^in0rz*N`vyDu(`~yTiz&2r+G1PZ*`H3eW%K=_t+Bhq?<8#&YJ1PX=I)-s_I);d zZ91E=KR2Mfo}n#9?(jRaZ(EPySUx9v+UA4o!;K)fx3{MsVzn!q0VBaY3zso9q{tYN z%!D|(Gi}5P@BudM?oYL*3mLy*u%mOJqubU#b3ORI5#0`a-q_RCJ2;R=?FLj1ZPBJ> z*7rlNbfpJ6T6b98(kuNgvm8GLdLS%GbYS$~)`3higRa}1GQtgUhq!>{)Jg0@ZEXB{x zy)-S_PazM-Gj^SiK!JO3n>TLSC3?xof^_<>j!ai-pmiUVXLoCwf|BmvmO<069~{`% z1O7lt^5WLe14V{ZfI`Gf#@)Mi5fshcA+0DLL+ zKB}vyutBd+YYb%`WDHnz4Mr30YPfDadPXO;Dw+iQUwTU`?nI*cK0og2|<2DA&M zx?4e4eMcL+V_>Lh>jY+uG*-;o(1n{hreyZ@40b|LI)_qt+j>vhyAq9s@uqbUJr{~4 zWrwj$FT^{Ha${#tMlp^_GgNIb+CH4tg!UVQY*)IVZ3}2e+IX0J)lg6XfD4+XAhEF* zRZs8EpxXu5Be6lz$`{|X55#tMWCnKicCuj$Gj+WJ#N1^F~mGiK5Q&27>NntP4d)?;M0Rcy}mb~bmRk{Ig7#CbMb z#`8K;UDTWzj_nPb)?beCvo#H~r(a<_AVMN4;bjI}ThY1p4t92Cji_P(@KTvfPiqG) z4hyAn=3x{kwvJv7Bi?0~UABHA&_c*oBX4b$JJWkH_OJ|!v61246o>h&iLmAmRFH82 zdoDAGaTf};(5Ms513bh^YJqh9?)84ZFvW`5!rszlY(Aeqv=7-L6R-oaDD-5q8Q7K0 zR?9=nZK3J6yQ3@JwqtPMJuf<6_|BqBZm;^~iML+%jdQXqx4i#`e>m}@E9Y$4Gg5yz z_fBu=p6oz6<88v=i7{)x-*f{#{?BG`vp>tGZMLj7t)oSPl4hs`vMbq3LzKTDANbXb zG`~Rv$hJni>@QzlQH;#sC2(EB*px!_gY%KwjQv@nBitjY01l!>1Y;Ru;0{+ zR`vc**`#5&wZW|D89;qnE=HH4=y$>zf+V)|r@GtIUb?$Y3^ngEF)+RDgI#;l{q>#{ zmPgQ#?#BR_fn04$cXz-n$6&>=?*J?tP9v(SSFBvMx~8^nO=?eTTY9hL-znq%cCaqp zHb%Z-3lxg>Sl)?!j8-Ba|EKM9QvqYWNmauAbxeK!VEq1n)cB=X?&;mzJVdjhAF`>& z(j+Ek%-h=2)z#CDd=0_bYfr1OaoV&mZiGo&@Aph=4Z z_isK3HAVF@2@u~ZngpyV!&<)MfWv-pB>918HzF$l}4KF(CdTz;0M}famfSR>wFgA_mwYSd}x2bjJ3R#l1k0 z<7YdIOk940hUztIWi%MM`Uh(#ST9{9@Cg($V13gb-L~nvATWd0lESiMW`MIgE_h(2 z4Xbt_X@x72V#BA+nhg!@3E?ta;F>TQi-`x*t+EO`5fJ8IuyoC= z#AxhGQNMJjw-?KC!ZIu~wx-33%3x(~`b?!+V*3xfH1%0wg<0C8b!B0|=iCYhLF zKx9{TCMasNT`=|TO!s4{+D_uzIx?;O9hd}Q$#A`$cyUTFF&W0LKmC$0+MX!PpOk8` zi7^^Da`eu$H(BsD%m=Z;IC0}*e#M5FfEnu{LbBlKbAoe6cAY>DtgdraO8KHZ6At7R zgO0l)AvM*S9BhFUNS3oKter!-*ULpObmp`U=E2$&Eb4m`Wtc<*Oq5mP&me~DZOL6` z;6HncC(Ji#!Y9NqN&3;rjhS#Zw4Wx=B<9A0zI=(mSOf98^qfOBV;n=O_X606`Uw*VZ15_tTIhVwbekNmm5tqy+aDb z0yL-l&J433J06n)?4C5XNkO&`oSC+ESY#7al9q*Py8KWy09DKuPRZtvhXqRRuVHLO zoD5vsdpv9Fjm@@~D`k?w$38AF)}I1PKtuadvh%51i?ThyVLoi~St9ZPg~pvvBbZ~h zrIc!q&183jt)&*S8^`PvbMI9)RK2zi?vujM@@>9>ecTbX1ABwDRJUz1%%iCa9)?qu z+jbK=Zvn9>3QJe^a6xixszu<_$FWluEtNdx(T<>G6f`1P3rkd+Ik2xcVR0!RBWG&L z*9pN%RKN-=7+Uk0dI$GlS&oepUKV72-%>#BuqViD%0w(M`&)wGG&fhV01klb*Wc9K zuzBb8+jj5Xys3E;mWP|SZQr2k6h3Tq<3N8 ztE0CwTL{GUn>6;3+qfpF>pqj%B$MqTe`^|(NVCn|IIy>FGtr?kHfjU*|3gt5_oezn zQM*&^8nY=sC0yT~-QF{>%il-FjOscz_%7I+X>LYh`n5H6!0rpnhPxqw=Nb$_&4ayI zJjZ%_Pe1qLxXteam{om~WEN)A{q|f1L$-CAJG%GwfHOSfb5_oABhgsAsJO)DDlMBb zb=o=Q(?@5_JomiB=&adu&Yye1y!jVacmhzlVBw<07cE(O@v=)sFHK&yJih{M(*5=5 z^&2*B+PvlJt=C+8-SyiWZrHhNcjJvsH{E=TtW&q|>$qcoS9ecuA10cE2Zj!2@013{ zEYz5@s0t$jdeX+A*P_QF(Adoda4M6%*l&is9_TO;s(aKl+B4YIi?IZK6!@q|O8xIx@;OFwf+$s}U;?_jQ$XGPQ^jGIe=nwatf*QG~KS)2ieh4CAjB??!_u4Wmaqj+0RZb z-LC@#4v5&Z1pYy#G>%o|86k?!C)Q-vQ%h8ML)d;0y3p;78uqmy2OK$LV))XPI@r-Q z*aa1Da(OWHu)F|k4Y~;ik39~d3MM0q6SSCcA_A6^ITGOw@=SyyT}46VNI<*a9R$yaMjjdC|Y9cg3`HRlQDy69?~|%*+EgWQzUTyptCdI znn1EAg%cWFI0!;egjwm%G%O~HRe(HGQ56Pjw-{L+I!MGhURa(^SD?*gZH4pF{qi-QDc#G zFX2j5VSd&#NQYlF^a-4e>*>abro_;i%G+Rj%t;@#J9cJiCV{)8U3msMz0@}~1QrLO zcF49{y|+F9S1{lP# zjDQWiwI_okN-zdm_ffxXPY>|0TOcM6zaACDjvq!M8z&ov+qxSwnr{=j^@e`#$2WBJ zrtLYa%?EL+A=AXQ&JEl*27>jQa4xGACV6&SCjZ-y1jJ^pB<{pc^N#M$>=rCqiW!*y zbzM(S@2-KAPcl=s!bsrZN7ECxb>r-l{k;hdWln?Kf_180C>LjNw&0`y3c`7{{j7N& z^9_SN11UX=#b^xg*FhiP_++MMu)j6EVK9^3?62|>f-xKWd$1>**_1xefrALhbA75i zdtFEOeqB{1V0Wr3t!Ln_16zn1rpz|3u=Ef1g1rHqnLmKHxo0mn=y&YJMgw@Z1KqQ~ zv744%JI*NGnQk+!UhiQQ0?9@(%3&l=Hm5Juqn-lbI0k;~p+&5ZHXlg0VkBx!VWkVF zpL}JW;^>1DPuMNTkq4aU#Mp9%1C?X(@9pC72zyK|tV!&wU2N4QH8M83<(wrB{H4uF zG+?yW%4L_~Bp!CnOx5J)^@DPd(N`-5Al+8)cUd_uo5kTwW@SqKD|%Di9j*H@^g=1s zm*F5O)Roa6JTi;nxZd+}-lh0cc4^rwy;m+>3jelo3F{v0LUpSSR#jD1SFNa8S+%Na zbyZDOZB<>>n(C_R>gpBME2~#kudc4CuC1=CUbCWVMfHjmD^{*pwPN*(niaJx>Q<~- zS+%lyFdew?mD_5;rwR%;}s@hd`tJbWpT3x+*#p;!- zSFK*Xx@L9l>bliyYN~3gYgW{(tXWmFx~8V4wx+ITO>I?eb?u7Um9?vCSJ&3m*4Eb5 zuBof4tFBv7x3X?k-RioUy4t$Bx;1M+;u;jc2B_B{+Zsge#o8!TFV7fcaEOD}1$m;v zB`KU~@IV6e^Ip|t5*BEa6BB>|&#)GU6-+>Smd$Au;L?l3GmyY>jSwhLcmGd1b8O&u z`V%>=i-uf`E>PYpaD?f$J?VBBd^iWmBOps#?6#F@BRjKBdC>EiH*qxs2bM4Q*pD+D z9I=1pUCcIXVFWja{G;I}c8r9>$gy2-OcIPMPNi^)6idfcuI%+it{Zh@k$7=&q@<)Y zQWl>QIVU#Voe`NCKX=A?ZXz-}a{jb=@%bec?jrY&*#5{<(GNsE8hI}ArO213ex>xQ zk*`I*;hv0tC-Q^X4`n`r?>3+`L9QJ?SA+j zmn~niwDC_KIr`{h$DerrM?U)3MN_6G=GR}f`G&`j|LqqZDL#Mhg2h)}^@AV(bo5i7 zj(H0gU$k^(UH#T;u4~xUc;ijC+{q!r+*T&Wq&say1c-u=Q_kML*>9HRiT3PPSFFGeya_IH<$M(mkMN5mP zKiG10>A)4kzbMO;^d`34dEV6XrZ$zHKm3|QS4Z#LP@cHAp`xg0_$!yhudH-?lhL`c z$f0!=GwS2+q34&~{m;X{y!g6USuAqTj7`_A9sa}>MQ*Gye!;59p>r;cwN1UTZ1_*> z=1;pcR$3f6r)c=$dtQjmh)# z@@udA)vqsGe$}nFHGlWsH@xx4gYWskNB;6tpZ?tEzxBQEk2*0U(6#lKuibXdZTG$r zi68vPUw-=YUwrO+-*PjYTAs=I3YHC@1K42<0D`B+Nq!2bKmP8d;H@gpZd&m-}r~EZ~xdAKK;e# zwr$_hbo1@a_rLK?ANY%BM*iy4pZVI1S+iep>o0!!^5}3^-?zSfPDOXmy!p+qeAS;m zb=O~hbk^((E4Ey{eFx?LRd;>pQ(ykdKc4#8i~X554Gg~ZqRW;af9jc$&ph|FZ$G^5 zZEvr7Q^ntX`HQ35cii%d;*#>|OP2rSr`d{`S6il?Sr5F-1A?0;p;!ZOIBX< zg1f&RyL(f~1+k(Thu(M2@O$GGrH3wvo?qg|md94bilc6EQSpqjhVq%kjm6Q}yt2}0 zNwhc`ff}6}i$|vvx#uL}+lwzKZYqux&7Rs2+Ze^A_1KJ}@~QQ)`HP#quGk%mhd&#? z`|0T1qPu?+y}5W+>6}su+#N+_MRSX8F1{qbrR>reWFuNV<F_;s z=9SGVy)HKVK+$_2n>ssIeKdCH8y6K%jmL){pML1YV%J+%gw!|0hCd#?AX+|cLIrIV zbu%a2(9vVy(li8(_Qp^Xb)0)*o^vGL;@o!bBhJj(Ud2?erQ)ZTK61&jD(}*s$4_1w zd7>qG;jdaQb6)mp-Z|P*^Bea&HEvmD?V@SlseSJ`sWr>z99>g2FSYeQEc!$ zQ+FJ_VMkBp&OiC+(Vfn7sa@$W9o^-8qjI-%@;i-x`rTC1kH1%W^YcGEdb8*J0QbDeEDTwI2JRBKI9e9D z(7haary}Q4pa<4y93oR3nPRY#BorE9cwS@;=+jc>yIb8D7Q$S&#J$0d6i+SL<3>uS z6mN@M0DP`ndyb0|<5S#4rS9ICTLhva=SO1E>9J|}S>%?x;C*y{q{b$ zw@2M+ZZQjtMn1jHaX(qD)=Ur@`~(rC>~7epFM7;^{tfz)&qjfw|g+{lY)c}RqN80E!W zuWV_NX{n+}^fE9UkMsa~=PZx{EO!>60Pq~r#-dyYt-C57=P$RY+(939+^b@EwjttN z7MYDphFI<_DTx$c7<+Tnsg12DanEsQ#ocmXonbJ>+uTQxXKf4|DDEnDT82-F8P{+N z-dV90oxQ$mLT}$7cHTtkCv3XH9iGD5Gmf*a!+1`M_2=9mVU54Z#(&|k z-oA(&e=viG3Vlm3PJj>GnW? z^84F>TM$6xeluoYKaj}j2xCa}-3Ej&@br+{gRRw#;Ds8z+cA5{TdM?Ls+eM7Tdde-*+iH(L=e+6V9ZpUlU9 z35>eL<_X3F#E*X!0#hx_GWaT+!T8q(;-9aH`aBKFmFmHX(!=`l#B`x}YrVAQKI`AK zO2Qi7V&kXRj$iI0YX$y!3-<)*qbmWtI&A}nAF*(Mg?e9O0fKtbK|ZaQ`UHD=wZ%6< z`bnFf_@|h!Q`k>c#%@5o_G9*owL}URjOU$q)u+LD;_&c~?sni=C_l~LA3zRpEz@_0 z0_8LEL4t59FT}!1rIxkJ9hO%v+yY#P7DDngcoT5eh%T-T7&G4j(kz=0=9>xX?m?JuGpKt8;ovy`WrQm=5q?Xk{a*~h4~Noc{!7e!8%R;q zBV3^Y_%|aQw2xkeaD5>C*--j75f1XF_(z5KE6-#;O|J`i5?_fpqsLF=uliyanNl1K}QoRktzveF&@V z$nbX&4$@co)57}fL^xQVHz2Hfn)$zma8TY}LO9r7^G+4gvl-!F``v+XP(I#_a4`R~ zA^2}1Y@a7%@y@KD#e7?q;VlRU`E@74LHgc-aIpP963Tz#?+f+ikB}bZ&&B^9vtI#Y zX?X7+;UNBfq4ZZH9Hi$X2nYH3BEms=UGyJ?@^>@BLH%(U;h=nc72zO1%6}ent`O49 z-)j-RIuL#k;h;RscriZ?cDjyl69(hE5U*oeFn%B6bvzBmHzMA%Wd`GKN4$=?2mfq* zd!X)v5+BU>&%mYldXZ1<0QFha6HiFKiPB4+JbilI8S>YiA^)8BoF0FPt?xwiHCX(^ zfKwYeSicuOEcieB=h6RH+Pw$AQr34IU*#4pNCe?TxwvqOq^NWe!KkQFL8F3Qmyfp{wQnri z?fO;gtB;>*`{ts4;||6@ylA(Nw`%>iU=MJ6g|^cG6x>r)le##pB05ZtQh=(}iDIJ$`oi&HDS5ZxsFPYw@8)dv!l$ zzruFRpB8nlx&2Qm+ROds^75+UpZ%Hbk6)`O_MeOTL(I?R)!Pc&$A7sz`<|+Nd{y@g z&;7l=&l@+_`&GBgz7N`2&i6sngI15oX)#`TS(xkYp)1?=xoK}bef|As^%*cdVx@MG z7dOw^#a@?ik6q=RBDdRHUGG8BUS7Q7+P?Ny^T>=}=C<8lm;L%CZ@ys1<1ag_@uOF) zu9wHHt`8}`=fOU2%W-xqY(L*Pq{wENt)dwZ=c`=TFt|PcC!%DMfvCedx?=Z`rq6m%P$!5B7(TD8{R<50`gb zkyqD$T8wYEyLx}*=d8w`Z(dz5FIiomT=YMnT$$o>Ve!vC-;?5YueaZA7Qo*RbE3vT8V~u+7Sr2Y$T%_$tl((xSflyn9z+`@ApfuPGA=`jf?WRBz`89%E-aE>p+(0Y1OE2kY}-QD5!Z@rR@K!!cQxIFqdQgW&Qb3 zHf?yU{q~eq!T&biK}CIads%;Uk=y&_am9++_Xe*n+7C19P~InYAG_LKzUJB6=AmM8 z>ZYmpi);IHwf&r;-LA`q;(H(LeO>NP+S{PoeqPaD{vJHI_*Y)cBYOMud3xCD*}MJm zeCevr{%*IgGsRaDttEGvC;RTJGH1AP)nJ!xALpjb$^U%ylYMQ!*Yva7VEBK&D)0Aw zihI9%|LPGiGd;`CL(2B_-c@_Dx?0ic=hcYK{YUHkZdVI^|3-7hQV6 zrnMi6W8}76++Y8>f7SM%%SnyaYKs1w%HzWAyf&N6!PRTw<-N_t5#MV+zJA-5Et_9& zRW4$6fcaWj9N1PIDYr7FEA^vis>5fgJFijq zPgQ%Tsbjc)hUO_egBNhn)&BJ})lIksduM5Vwne>wyIVCM!!x+CP3ybx7@on6^U)t3 z!!x*X0s6yZcm_9m=ns$K8C)-4c#2DHb>&2Gc=0N*dGJQ{0A5_Fd2p4w^JaAb58(vP z;Lcy`criSMowsUz8y>(Dcm~(rrsH+s03O0IJcFH3$9Lfl+=s_-3eREZ?atc8-rr5Q z1NY%ETpQ_lK0Kq#Utq<>F5kaN9l~?i`+(Lj;pqo8Z+=KUfG2PYXYlC5I$rP*bqd!% zs`(6F`vZPaVs8)cX00E=b9i!#)=zF#r||sKnm0b9_TeEM!!y{4b-X6*!vlB%x4x<4 z)l#(&x4)(N5RUKGd?YS|JMC#=K=K??)*se`M;@` zaO*+MeK>%}u>Ujd--Cny)I9x#dIm4y<}bBAf!&8RZ^4aRb01#9{aX`VXbe# zGdTXW)=%LX-1#r958yr=!Hq|>{{-$ls(A)?exvyqp2BlDhvTJ=xA?8v`JK8Bx8M+N z{9gOF;rbsn58xTR`c{$k*W-ry#KAsa12}o?W7Za6^V%-z0X%`T-L<~4hdP2Y*n6VZ zhp@Ac=Gp%0;2?E$u)4od?Hr+Ql^>uN7rXs4*nNiPDO`W1=50?sf^&FqwAQEP2lSQo zsUNHE!wKAfuGS}T?RlDa;VJANr}fbb)eE>){$Zf9{zKSlYrZ^Q?Vg~Xov3!p4-hNk zkKoyBG@qWMZl0?iz!NxyGkEk`9j~@Y?ZeYR^Je)6pUU(7<-zdLWq_TZIID(yl*86Y>kKqa2yGqCFy-7U`)%CZlLwF8*S8II$ z58xpjzC-&**QlMpRr_$`TFp~9hyC|xeFSIl0$#$&dokWfox$F9nul=bdd=M%)h#%_ zS@ZfW>Hu!us`(7A-KKdDPT&RXd`$b-;S7#$*ZKuKyhHN@ZcH@q!QRI;AK$57!ktfP zUjMW@h5gTJK7do$`GVHF@Zvj~x9(Q==zBDu!1M2G?%%8K!9zHL`}b@A1dbojd_GeT zf25B8S)Ke?o&Q9=_^G=2uj&9^KB#&8b9H~A_WnaXgO|V1y!McK3{P^+i$4m=?}OIE z>d~*&-AB~*N7WHL{Eg<$Z`D0GgO|V4`ucyXyYTQ2n)m;t&dgt+?eE8Y*VP|9tb4nu z-QCqOT(4=~hoi@7-uW|ix~ICempX@sPtbh0x4Lh>Z{BWC3QzXcdVfE44A1x1e7Qm0 zF<)?Nmp6u!Cu@E00CjYrdT_AXan)XVU`1vBWpt>zzEPb#P3<18?!gJ{G_~G`Be?e$ zT0c5MJ%ek{(0p{Hy7rgq%u_dyQn#L^j*eF6&sHywQRmN9r_WapU!b1C&I>i~w$;Oz zsGXOo8!uO<@cb2;*G^OiC#h%fusA`n`uJ&{q7LCH9G$B5-f8OA>FRO$h3Un`K0jJr zb*((Xtde)nQoHA>Q#gOE=Gi9o@I1A%MV)O^H_uo1;rRucPkZV`aUy7SdzP<*FHtw% zpmr})2k-=Ly;19(@(UP?i@m)USE++HscUamH~(7QeycixYj4xM1^aLS*Fxi&n+-iOr_xEpC6+@en4+O3+8;NUjRYsHDt)!VakhdQ08vyZFOPpG3i z)yZenUaa;%r>=co9eqI^e^Kq+r5?i3mo(24_2kRy*;m!>*VGfZ@eR#K-&A+Mt@gj8 zp2LIhY98DJPt~LEsr~Q6|Dc{^>Lon*q2@Wqq8XOZ)pNwVZB=n@ zhB?R6=7X1N|2e#bz2mjM4^QA3TsuMgx8MOhf)jWS*IuUMd$11=;0T_=Ib44^mIrs? z0UW_oI6YCvpTWJ8G!NnKt2OseR`=od>6&-pA)LX{8QOma_qv*ga177j+L_wF3kPRu zK7c212D@i#|0Uc$2RYn$t>%3=f_s~^K7yS<^FBO*=kR2+_IJ)xdvFi-wrKqT_O@!? zyHXvzNj-auI(e(Q5vtv*)xkT|`8(DAyVb3s+P_9UyjHz{)9W;MuUFS^P&>sWE7kp_ z^hWjg{p$Ky-Gp24{3fmUKA;}JDeQbu>)UV$r|=T?KBVLI;0T_<^$%oWsqJ z=y(AJ3y1Io&fq2N-lEIvzT!c-~b-NF+78v+jRUU?85^%f~Rl}H$JAzYr{Qw1SjwuuHCNVdvF&H;R&3z8otvzoWz9z23m zcmcP~IoWpmn&w<&>o(klBRGM*uju+`@B(gpRqOlk5RTy`T>qMm=fPcg0;jO^bset( z_uvqoe_Q*vzN2p3txoP!XK)VJexUUp?87~H2uE-V&)_9o{~^`~?!Y}bgd;eCXK)VJ z@7MKd!W}q(LwF1)a0cga?E$P0+=eG`4!biQZ}emJ1Rgzt98Tc$QLUftv8(K#IyH3@ zu0I|*+<^nw*;D&B;1=w|v%R!`F~_Z}zX#Xqns@e9JNu~z`>Uf3>hu70{XlgW&fxk% zTHk>~IDvE6Jy^%7C5hG($j>Ud4qhX-&3PvIPH9HPr>!##Ll&i%K){{}~C z{|HWC=UH0cfI~QeYe#GUHr#~=@EEQ=8{@%Ucnrtz@E9F0g1ct!1AF`R;TT@Pjpyk2 zJ{-a^JcsKo9j};1T%PZJID#|Sd9L>N-~bNc2u|P`yo4Li)8)6|E&IdFun#A&^8)Q3z{?kF-ZXR1+Q+jG$H!~EdxE+JcVOq0TJOQN zS83jX2XF?vCu;uz-1Rl@!67_`V|WUWPtoxwa0<`h9Il6x0h&r)~c4EE2~`WSZ4(cFh8u(w(3!}HYbE$S{@+p2j42ir8C!`<^W_byQP z;95^}7oNi5g{Xg>y7qc?4{l$ic@D=HYu+()x!C7_20NE({SZ#zIh_*M!k$MRa zZq|GXH*e8AgcG=aE9!4k_itB^?@(v3Gtu0Ghj8PQsD}f11h?+g{sVXfPv8aYd`ia) zV|5JYpVi#`oVxdU^%$OiLG$_-)t$T4133PY=5u)PWzG9vQF~ui_uwTQd`;_{Usrd& zsh-0*9H&}8_owSgTW}lhz&_lA z`|uEs;0c_Ek_4(!7{xDSW$7>?l-&fqzm!_E_Qd+V?ZdvF`};T{~q zLpXvba0*Z18N7g(aBXkhei!!OHtfRzJb;Jr7>?l-&fqz`gzIMRdHZ_Ogj;Y24&VVi zf+w(@``#{phI|e$Vdsf@|J2|*+<;xU343r0_TerZz&$vGhj0WZa0<`hIlP4H`{?c0 zgxjzW2XG$_;Sn6cF`U8~Jco1G*;lu(4!f`iw_zU+;65C}BRGO%IE6EK4(G7*By2zI z!XDg)eK>&oa0rj!2#(EbD!zrA>b2x{c{jvS9 z3m0GKDEo75*oOnS4~Oswj^G$h;S8R`IqYoE?Ww~q?7?l=hXc3|hwuoF;22Kf44%U| z>@=|bunT)|8}{J|um^WwAMU|@cnFW+2^_;yID;3kbAWDd1NPt!+=ct_5RTvk&fo>?9EkOWJ-7o0 z@Bkjc6F7xua1PhZoa=V~yAC(u7VN`4ID|*=1Ww>7Jck#sbFkk2HMkBpU>9z}9^8W4 za2F2X0UW|(ID!*6g=g>_Uc$~(ar?n_*o9lL4|m}{9Ks_wf@3&^Gk6Z?aLv{2ufs0v z!EM-wdvG5fz#%+_V|WVB;T(1j!S=!~?7?l=hXc3|hwuoF;22Kf44%U|>>P^ihh5l% zJ8%FG;1N85Q+RaT>hs}pUiWh533qjP*V^|!+xjr>Y$@-}Uz8or!#_dU=Vuaj81GT%EwKnSa?X zuLaMq)c%93U^Abx?caHe=02Q{G+&tcZ*BkCt(s46Q`c`-XK;9j=H5hI|Aab)=VrcB zyFLrJ{-E|Bz@eG1)b>w*uJuzmH}jL)df&`9YVDc%M6G+UXXXR7d2Z(Wv>uxIJFQbQ zU#E3o=I6AonfWxWn`S;tYad>i`SxtS*u0uA#=5gj-MT>Ch6iRoIop4FnbtSosP^F0 z%$H{SM`nI9>$;iW%i1^dZ&^1#rsLN>sjkDJnNP{~PvG`GtMhSf-hsWp(!2$a%zP8J z{}>*a`4DXGoB0Z?b2Hz7b={orZ@qY*Eu!M6_SJv1NgrHhwiw zr>!2mSUrN5FVVdIQgv?5zqPmj_$1Aj=6qY5_fFP)2nVmxJcoxp&Ap4%E%SvLc6t5F zHTTW=gEr6M%$(n1bJv`2V%>#9cmmI0r}z%N@?yvHzNGHJaiaMY9-H$s?08FaeulMg z&d;#+%=sDClke&H&F`zb|ELb&;Xi2}XX=GHzr!xiHRo4Yci<3C;2duLP?s0LV>p9r z=6nXbK5cja$M6Dn&G`&=ye^!Y^A~KMJ)-NM!_K3c_kORQ!;?R1?(DL@e_ST*8f?x_ zu-BPt{tmxz~jpM zi}Lo1;0YYVb2x_=FVy8bFH#3>^#Ja@MDr2s9Itr?4o=X#*HOoC{Y1?ZczT-V%hT0E zbNr}%yfn=5qSiUQI7j<0&GCn}zHyD_Av`t5``P-wIo{9OHOKE+FX7A_zi0Ew4Z6Ji zqiWwAKexRe9-HIoY(6%}(^)&__&Mv2Ilj$$08iklIUdc{x6JWp*0DL>%(`igFS8!q zrQ5SK$B)^3XpR@N9+~5@tW!9tynic4?%h=US6s^a(v6!-t=qp}9ar8jmGyP={R_5# z8_wXQ@_wf5?^WKFJ>PRxAuw!i;$&1cV0*N##T zk5$jj{OxwUMqBgAN$SMRPj2h|H)!58^EcakX68q>PRxA8*14G<*xJ8C$BW>Zna|eN zJ7&IG>)v;@e|@T6n)zyNy=&%^weFs={`+n1{(y6Mex}w>&Q>SqsH1b$?k05zCvbPO z*3ZvV=UddXt?IDy`=Q)E_wMrl)$^nLB(7W8-`>6O^jqtB>DF|8ygdrfzP+B8PWD)L z`O#Rq`??=jxL?`dC~xzqvcFJ%x8mXt>*McPy0Sm8SK;{~>*GD4uyf=U+Xh!SM{d39 zlFP5SthnBM#pWX~+jh~BmtE?tT^GSBz3Sq%>&+KmxWzg0@?KGW#pd&hnoG+r*D|7Q&u6<8$`S`N^?e~4!YsY*JSw6tcncEvB5N1(IYiAbu9aBlsQqI&t?oZ_oy4S z3-4*>Y&MOV3(P$%N_+kZ&D^>nC^m5~wTf+o`YKA62mAo^Y}^bpN}K4e?O0$Q(Dtyr z1?EPdjqHsDW`=Jvdv$?1-}efOf~3_(wt@-`Nc{5I0>JcJDe5QqPxJe1j-mu9UCj?z z*uTxf`rFLI{=@>1U@y-x*S7w}yrW$YmX~d=Z8wqKpKYFPx1EI#Hs^O-$=VDylRI2* z{;Tg_SchygHEbeVG}2rX7GW;SEH`hl1(??j_Gf1^%~`gM=ILPpAtBj{Qdm~ggc1l+ zRV5TsU!{wAad5)8YEfz=m5Q{k3~?024~Dv?si~>1T*5FCr2wP69_pbex|>>8fuNV| z5cT!)-A#O_!MbU_N_X?2A(PDkLjufpBmCLpnPx^rDhnBDE{?dVbsk}F-fi|sj|tuj zlxty-(tsAWFlxS{7;DY)E@|v+lNk}PirqU#bn{pibn^h4hF#mjR*o@)!{@V0H<&BK zw=MqYTMR|g36W4{-&7s zvZ=SiKj`X6 z@P&$c8A(qsUnlzItK1@R^r~X&rz6n}kL&Ljo`H>on-$%9`*jidE~fMP-hS;xzP;Hw zZZPx7FgM0+WK%|L%ZNXvntxB}%I?V6b|MA(@Wm}=RG(SeKFQXSf#$|OyV$@CGynSG zY~nz(^7=9C7AS=3<-?)6Q#B_?I@zx`3)xK>-a+gM~?xjC7wD>9RBIl%Uf5|Zi#)j`WP4-i`R=_sLP4Jks)Mh_4?etOfk z;W?EmV^hqtWAg)7z1M7FP=Q~YWaf|C!VXW~)?s|G%HEg^Sg~f#3qIy86B1isFPda- znJ^~S?+%=mLbk^70~f1!GU1j#jL_VD33$==9npc zcxDfuHrrNDeNZ)9PhGJsu;49COLTv%pNgJ-LO*mQ6F~ zU!(Z0Y37z|6c{m0uTeY%XwPa%I~{0Wqqq;y zzD98p(7r}-EYP0alC}+KU!yn>XkVka4bZ+uF$3CjTGIY~syY7}#Xp1YU8DHNspi=^ z@v+`|er&3u)D@CqpjKo|;~DANe60uQz}9q_CJU!;+jU30s@Bw=xlx;>s;`&>=k;T~ zX7l9(wZ-a2^USfp3LZC@g+QpVZ7pjh>peRx|62AniQB2aQNJbK16Z#za-?2IblJ&I~`h z_UazC!}`#}(0!Wu@x6Vu^?>IG!p!EHIbwNVZ92fNLwU9o$B8a^-mCr9> zHZw)-#+D8ej^=^BR8)LlH+I_~GyeYNKHpquYT^q^Sg47GqXwD7S1$L-{g-E9AHqE7 zE^JuYjoAj7$q&yr7d_a(8Z*r0k4!XQTvg19QqAPQrn3DR=9)F(eDmk5qlw7xWB~FM zGhp?vY&{~6GxHw0g@vb@l@FydSB81`p>T8an(-`nw0ULCd{#Nbod0M%E1O}ieKeKz z8)fc)G($^+;p$j2T7Ah8}nXH#sl!Dk~^J2xLY=dGxyeTy7j`7V}Hj^d8NP@4)CMiDVz_AJDfahoR zcvR4-1)Ya~N-bz$XGpQZu^x4U*hGbiIxkku>hVB}Iw7)7`c-vul~(5EibvYqBk+Bd z*P6@$V-wAXH_tN5pAT%)MOF$JZ|<*5HXnX&Rs^g$aCvfrUNLg>`#3;)BBB8AUxm;f zU_|(CoS9txM2B2~;H%s&d^|H{LWJ4vrBrpi*<;I0aDTJRi!Urt)6Kar4pMu<-&phb zOT*MSD2*|{dwz@>B66R<(5T+PPzKs_WNVyOuutRJRa%@@3I)At;R28tV0&x>ux&C)5t1$JOC-fy(nXCK^a5HmvC@%}xVs5rlEnthe znW0J>QP9j?DZswUZDx;Gd%G(9k-4RU8NGa=WKPDHtp_Vx0XL|%s?>@)C^uv(R|&+_ zZ-O!$Q)Rre0n%os#wt$;*kUSMxl_OvQ=^qK0o&V%22o_OX8YF@%=_O6aJ2z`^|ETo zQmJH#J26!g5F@ z^;IS}nG<%}`NON2zkMdu!byMOM#RjMm^l(Nr^(!~GqsN#DfORCW3kaP$&~t2(+2{U zOsQv@)(hC)Wcs|B5_(k7JS1t>OPc9T<}GhVSu~ZyP2;eRyQAMUeJfzAqhB@A>;ak^ z-b^u{s0|2xT+Dz~l5Vl28`ot1`sN|_!tdt(UGoFIJ-e?)n`7#hvqx_<_t#};C-=f~ zynC4DpRVv;)6__F(e6y!i5zfd9nd&jb+ai&(d;~D?%zF&`E}ct`u7g1&0N=PV)aec zmk<%rI{s?rzm=?oZUBertsvI+pJwG-$?V*D^T1oP*iTVr#@oBtv~K2=x3{xhvF7%7 zX0gNqGwR*7e@L+G7jyr+%>V6TES8=}~tf#%tU z`RvuMX8yNZ*c-`ahokXq6$tU@Vpf%GZaKQ2T^?XAKemP~k2O1dx1HUdZ0`9kO1Ssg zMo?gb$2yIpo|A2MIG(I+L$;3x#fHA$tQ_d3d!sMRHWwd{*L?2wUq;+R!{xRSFW6Uu>tz`>uG*A4L+Cp@Ef|=YH-=g@13FfTE ziR_gr=B~z!*wY@oqP%;7^MZckyRbwfJfp7}^-tQo?X@AvEc<8IK<_H0ea$r+{{0>4 zFGrinKd)hS#XP(|N_zw1kt073WGk;Tqkc(cX+N41f5~?bciNu}ciCw3tOxo959PU* zcB~m~uKYDhyCmcrZoc*F6fJZ;Ozhu=vEAwBtlx&W5Z3YW=C0o|*fZ(omEXp+xO;lM znSX8!J2%AKqlGMMFAQWK^x*l$EY=MDR~L5g4`%Yec3A=)^=A_L3ift!*ij`>4X*nG z)g3?H9P@js)*Gbv;O|eiSPk-ro5>f0TNFmL;9ABLJo!UYlU@-( zER^|+)K@$qfI1a5vb@@S=t?~QvXdGTVk9-t2n17yaVsZly~bUdYKwFDH)3fV#8w#M zUTd4@=r9~-e%*V_KYkYAAfxN9hsMf?alQPcpzo_pLQ;x;7a!-NZs7sGY6QOt{={Pq zc+jNiRVCCVKk`QP>UG5KX4D0EdKHyWjj9R~%7hwNttJmfc>UEQYb;CS;jPqxZS^YZ znX#zEo8 zk_UsLmy@7_SQ|kI27nv;;IWw;m4FBwl`=)Pdf^Jc z1vYq^^aP@1*iuC6en1jjK*dN(6tHLCSDBBLgba%K8ES2&q0uW>39O)2W2n_-HPng> z@C;kEfQ{8s!ezVp+>UA(-;k_E^uQ4-f&Y4yq~ez=sOV)x-LMhwF!1KnEQUw;G5-W> zQjf%;UY$lFb5B0%gt071hw(Mnt2eOvD}2KMHIq+GX8zgM{HyDO zN>19ZR~#Y{1miG+4ADb@>q$xgPtLLm8U$VT&{z2ve>z=FYbkdfbtS4sdZVaj*m8tV->hX>m-wX!)jlul5NJshj%q(>$1L5}#T5zyud5*T{9sC@!Hv4- z2(G)0QXKUaLP3dl%@$nO8laswfgu5J054cf_|wn7?3OuTm&2zrb(p2X;cXX4np0u->0d8jZM5EzX0sdcg7~F?KVx;{W#kQ3AR$z1sElP$lY=9LY)i?N z1te|5Mx*1&0FJINrVGpx zSl&pvuqWdoE&%nAa$-3+RZ=#O_o^iYW&AW~oDJ^tAswYRng z^tOY@z^+kwQ31)I;#-2#6KjuUs$o7Y+@SY|@(#n)mhREJ zL(QlgX7gu;sU15R;D>_^B#Py$uFz^w`{gk8Ru*#SMA1WP##TjEs4RuwoTV1B{E4+& zveX~d&U#s~#5hD`%F_57qrmW<9;HTf#jf88|MkjD(&tu#giQ3RETUJP!^0=Dn8ZA( zD3;M}4~-kE$toGk$SN5{4OjR#H>(5M=v7i0%ed_p^}g{HVlZ~ge(a#Mk|Y(xtSLk$ z+jEf77J6z=HQGi{Z}5YJC1o$1S0djgkLJhf0!pW3QCc)d zq~N{DR26z_q~{3O(HTbz#wk6MU|!ZmjrK7{t-Hu02CHLO=|w&dQdfyYFBIsrfgMl@ z#Sw5D!SFW}{%5BMP;msoa|yU+!=MVhE)xg_wgn!6iX(U&mA4{kIfBLtID4i@6h|mL z4TEcT7|2o(3eKkjs2~WfCkR#D%>MhbLC=TRax(u<``AQXbjNMA^B@XZ3A z+a`)kMFnw%GPA=Sy%c2o;ZQZzf2qKqo6e7pRg=eIl!9H$R4m*daLe zYk*aLcAUC6;9xI+%XSOa!DG6}cMelC!lFs5Mv)*Lo2cucM*M^E>ViU&fMZjHM3WNT z6fR*Y$xSv1lVlv5!X!)*acm;vYm7=GNpA{~_fz=O6VxN!OMb`RII3w5IX3%=+m6as zBGo%l+-KNU@fUK{-mLyIkIGVmL-v6->SZ~Crs1}ddA#jJwJ_>+ag!)JZ9fQe;9N}} zpmTlg;}g|P)s-UZI&J5r)gbWlBf1|)#F-UfmL9|1bu6~2=H?R z>zn5YWY6gOW_YfzSJjKPzp!$tXe_9eWt@QkT;LOsZy=Ngm1{I{Sxx00#+rY9MD4`q zrK$cd%NxlDaeH7WFM4?+_ek~a1})*rln3}TimFA6#%gIimyz*wNaw(5v%D4RKDdU& zVOgUTOGAt(@N5{JEO45L@On^{h+EXC;8jAy3|l}Wbv08E;PsJe5Fb57?Ko0MbQ9hL ziGsDm4I1Wr*)GG7#PZp z0yP@;i=Hk}dk0o~ZCGt-p)KShi5W#xl}=#6!Jz+Q{2S*sdrxsJKHQ zqD}QYvt^i{4Cy>#4m1t%t|YZirmIBi>D{bC7J)(w7D2#4aE8+3cy0clo$x(1oTdzr z6afGnt5JG(2I6q}Dm_K2CrAZy<`JAG;T2}m*9i$Xh(g7{bqTIqHb@8w)f1^&DKD9$ z#)gsL4BJw2waUnqGHeC>g*j^6G?dhm5SYC-a$XTeHFerTMFoU0+?-(xaKp%?Ve=O- ziUl@>p@c5OrigN5*@epre`%2#?YaQQ19@5%hf_yR`sxOu7`{p$AaJ#@u@vH@mqM`r zP9fsOa7vB%pA{nSKUIhpEDD9d=dn1Lq*!|Y0EJ(ARlV-Gc(+}GEZ$^jTudDV{YjB2$!I5i5HLWea%4yLPW7H0s#ft=}=!5yuJJepT zlq9;{rbOI!q;!|?IteFAxRk1=*dFLu#sLv_cO(9Fd>xY`l?b|_lY6Yhe|sM zfi_;eq)6@K(#1W;5Xno@LTY=6rpw8Rs}7bMLlLdn=z9||rW6PUjP-Lwn(L;QPo37~ z)PF*zLx>(m)<~bH>|vqT8*TEa8>MojN(jqnlS9;Tp@yqjq&d9pJT=v|I+N}%OQEzV zi&DEl_rSl2#JrBg?0AyI>v-G}YnrI1bE>F6CB^B*ts(iSg>@IX5674iFD$?-7u*;u z@l{g!*YngUmqh$yykx-{Nhqewg{pd^Zr^>Wf5 zXVQdP=R0(egZbpMq>u zEF4)QshaRckrROBaJk?{4c$oEAyWOh5@Kk|DC)s;>m)o12>7J#s&iFS)=36sH19FyxExtyp37*J(1(yF zqXyG`{?&XnnpI|TZGp;J<7IyTGVpm`yj}BmtpNRk=QM%=y?VDu@#qRlM=wjEa@Y~V zOYbaG*o(nSmoAEagR~pc-dK*HA9Ms)c4G=nUF=xiQzUn+Z%kR}5eV$_2q_Ei`~KfHOKmVhH19ZB2Q$BUYj*H_#$$q?Zc95Xn)R zR6?n3l}KwrqR6+g8W(AN%+k%W>vs%6LD8ab9eJ@F1k;`p#-B1-$M(9Xf5snKqJ{)| z`#Ss1^6g8=+cbM^h~(Xtswp8YkbXjkD9O_y>i1_3(XGR-L-2(ZDiwV8jce0jb92q{hBV zf6)L>yIUQieo<@OttP1az8BR%TPg0I!GP9yc|hmS@coOW#~-~!9c^!ca2Dk+$f7gZ z#NW}?Rcek0H@X_fKP*-Q_|!X8|Kv=;7tL?IoD74nvR_C}Uy(;C?)t!`KhD2eruGUX z%dh0Z;}dKM7Cs(ss3T&glS)o0Au&EsfRz+|9Mv|G*73g>YCqRfaoZ7K`3l7nP88@y zn^jajz%F3Dlk^d63#ojpD8_(*;Iefhb^8o%u0{xU^zm};OPk=w``@F+#1v4a^gK$X z;b>la2XNyab&xBE+9;(iIdUw2CqEA1)?KNa=HEj?Yo3YF4rsa zzT|Sfzku~H$>sV0f~CZD>q+MNM%vy<^Jc)82He&l?(@v$YM84vt?O(P`xmL7r|Hj*huMk>#p&${~R>p(U6Bg51X{>1%ifNMKEssF}b zQ8Iz7UO~=@Ubae*tqa7);B=ylA&mP7{suM{G9ioGZ%`Gsk!8K|5~?R1*J$b9$NBgx zxEJBMk+BM&f%H-M^$)0>vNz#z0a@8<++wCq1{rv$1}|d~rFs?h{Tj^hhaOO4)K$Fa z18RuN6h{%LLl(`B(!d!GqSc}!^5Imp#M2+o6Nq{&;XArTi;N>Kic)(dr3n!tHI~_h zARGzdE?E#N3v3b&kT48AT*fl;T^tECrHy6e^f(eAo)z~m2<>nr(5{)WtWm=BKo(4e zgh^V)vIYse$XpxC4#@%AowWtt?n4E3}btiG<1O!3hQlqeh`biX}|DSg?5|VG4d=Q%l0+7aGgvNZ3c# zFA%WHkpNmM2s8ef2HimY*Q;t|0VxkWgOM;PmtM77!lZn9)m{mca$4K>P6sJ3Y(C5Tq}+N{ zgM>-@^{SH+4ifDH1C6qPrjK5ALBce>^eUQeP6tgty^5xz(?QcyucGPdbkOwGt7tkq z9pTVD>>$l|r-SCdQ;ihm&aqK6|DA)QMFAQbJLD{j>8WJ6DxmU2!VaQdo+xxfj}g_t zGVc&@B);tqmoVs%2B{+j>`4coX`?^ik@_kHKpM)ehYS@|d0OvX#C?p^ zxC&})TJJYRIWi^cb(0EFLCj6Np;p|-Z!zHItJF>v1)c*y%X_BKFnv>&NO55Agp+fQ zG8)JrHAfJzj#cXA#F(!#N|a--?*tFvNCoVB$^rexE5O-x>gw_A(gNW$JkluH;C`er z`_H1woFUE8<^RgS-}NU0e_LMDAW8R?1*8oZ^pR7WVJ#y9uVbVRnk>YR0eip2PtLwUXh~C0J$dp zu}o24FW+>9&#O}HS{`V|qgBnCamY(EMtW;T3k1}Rg{^G8|GP=;LV-B{WNeoK)BnZT z3ITggr#$o&%Y_8Q5h)PaXkjQHx1-DoL{?)MAJ&4^i<`Vg)b7p<;kfvnBPMELia>> zf3<}el1DkM32(WSe4(k@UA{_`zykM6w2JIv!!vATHauHPYbh5YmV4|dmP}D!FHaL` z5bG?AuCGF&U|~5;5MA0seUq2~J;7NaLuX8pDICp3j*N6-XhxDXvvyqJQ!lFaWUJFf zqS+kTY?f@+dd{9Fq+j ztq%(PPeEEKo@9_gTIhUWLYI}{Fd8ud%m+E ziV`I(1GFN$gk^wM6e?jEpcMs3nDoae@|Um-(25iZ%K)uNZZO5WWQ0~kyDpA+@?ni4 z+7EEV%K)v2c8VPFGC(UjbV>9u9uyibI1evj8K4#Im9PxZigrs_253b)2zJHG2(73_ zBFF%(s9M4@Kr1Shunf?O)=8Kqh*4A`VX~A)(JBec0IjH4!ZJWBS}I_dBVI;mMGGYY z#W6(9}BYv;#3#5^xrh zMD$7$k+YCQrdJjV2Azd6aI0J;VG_AsNkiZ)ls2n!oxCptx5`op%fPL&`jQBUoP{!S ztE`a-GIFcjAz@M)y>hpNW#Cr1SHf};tgM$XDWzU{NWwC3t89?44BRSDUJ~=qSx8#1 zS2jun8M#$nkT6Xfy^^MpvrqWcyaTu|;SG%DCmP-Xob;63 z0UUWuV2Yct2{;c=Yqt!c4EH(srQ`wFq)Q=PZ3qZm;s!2k6yVKq!>I7q5jMR46u>j& z5jKP=z-tg9K?{EMp_oIW94uT55f|fvRF3Sz8o@x7ESbkfqs&){3J?s4v}N!|gj)uG z#K<29eLeZeN66h5uwhsiXyW}Vvwzow=vqRV@V4MdKRLbUXLXx&19{dAX&I>t@JhhI(PHGV8Torc8 zGkfNo{_T8sf+*b4XPPC4tph*~e-wQ_mq+}MO%tr?LJ{35C1LUQz7&$C~#nboGBj^A=uZuf{cNUaru*r6gJT#K~8oS8nyOnNrg4 zi2E&K*Kt|meqqIjmoavYnCw_#7bVT6Q4yzj;hc@2fahb-#W0dcg>Z4%?XhQIB$28) zZL}7k$^i;|(dOHb&f>(ibu_eD8ObK<6Y@mM`UvTQ+$BIU%TQ_EN0AVKOCL(9kw9rK zoan?`Xd-d1!%Fp$Pym+>Z<1--)wsvM4ov(_=!cP>Mp7GBKp1js57sX#?__>;GX54v zW|FuKp9d1fj^W(}?8r={YFT!=zdDhsWl0aBn<$3ueLIzd*J!?YGSbvfru3IiztJXC z+%w2&@p!ubIjQhOoP)r8ffR}gEG4)``4cDIBxj>eidDkVjW{z#kuT)vMjRTW$QRPP z*#%3+D1|tRlbqfrieXU6$udY{9^A-1&fDn*&)KeacTFLW%~(&Kn!5|`lXTnUEX>nE+*XYKlr|sEdxjY9Dix+L-N*Avf5_>#f|17+dNDs%ItLdS*b6J|eg1C}WaUcx3lWr$?eJG{w zr{N@*=BME#mgcA7B$no<;UpIK({NWA$;FV5!$Et<%(XcwQoT%Sg{?utCq?(1wyz{i z+UB%D8|{7RWdY*8 z(?-W3tOEf~d2HTuUB&m;;N2TySZWdkz5u(f-uf%J%Fx46IkCf)DH=kX))Pzx>{SXF zA0D7}^%b9-gg2%@GRD)*3qM-xXt?jTh1O<=+>*i_XB8*E6#-jMBdl|wlO(6Pttf6Z z0%@*UX5CkzZE$!>A{esh_$pTvBD_=*F3dMKpl*ARYJEfwPk&u~PVtA_Gp2i3ypd+_bYYRRfRF`CQTN*Q+HGY+XS zDTCrrz$q6&EEs%=)R226zRF@OR*c1DBYfe36MyFrY@}DfM*7x9HE2X@WT1MEWS~GY zaEEBaQz3Aiaa`_B#Vy|E@kEmhZw>LGU#cUuz0mbXzf>cc?RviDOZ7$m)j!o(e$Q8G z08`@lny=J&>&;H~@mGBRSL!bN-4V#Xo!T!?6lpX2A;9j}YDqwbz{-(Wd6)Pr9a*Be zAtHE4sVpNYO$*#1Rli!6FBAhkE$~%Qj*5U6oMNN<6-zJkTYgm|f{jt3LUh*WK(Mca zlX&t!)D!HjK0Np^yz5fWZ#)dIqK*HK&p)h2vFz{on!{=`dwV3`br>|`rtkO(OkW$x zI~;*A-#U`#A5rtg=a1`-sC{Ez2jzxING)%+i}r%lwL;cDc3cCyzcG8 zGaA%X7Wf@s+yISr_>Qk_fX40{$@gH&zT@W_ps^(*dDOR%-ZhesIj;8TM5`5?@mwl8 zZmj0dFJ!mz)5p~S4Ll!y{#$jLT@#G#mJIBW3|Oxz1*&|}QFUo-y@#WO;@AzfP=LOW zNBGySyu&dyD#;t~d)U;rW}MSjEcifppTgJN#iA0fj(KU|W~{zIcy9g+_|wPK?kqE$ z?>VOK@wqh|PVcPxmuhcotfVpYRj%+Cz5|r|kMRTFfe2j3_!Ufl&EoOLA^j=q-xEpG zadmXax=0)wMT||eoqCMVH~}q;KE~Ia0E$_%g*UVKVT5;x7KZYX->Y+lbZfs?hlLyz zr08|EW>WKb=aXtoK;4%(-{k!9p91=QQcdZ&Itx67733)dskd|O2eo@(>thh}KSrHJ zd4H71Gk$=fSC8@eKd4b*hrzfNmhBdbjqT7>EC4x;UdlrF&L2RH%-*&8e^A@0o#qQV z4hOZ7tKP#;{iwz;9~i?^FfFA$n)KNrU{AgR35VK+hBQyd>xGqskDv%K8P46WS9H|M&etd5qcd@FQ@nHnk&c3MlM|B8cWowVE+puD~YwE_6>visqFA&^PBi6_SI$n z${8TNE|woS(`=l2&laRhWTkapq~8;oT31LTgNsrCA9z-cPT4m?%x^*u&J*{Im88)I zSz@r26h+si!8mO=3#LBeB^JOg9p!5`v*gZrpu5gVl<}(}a$LejAV*aT#aQd|+l0Al zUxo4ZFEMyT2dbiPt|3)uO`beFSK@P3*4^CDIUsN^(8c{=XjRPJ(h-IOA1mCh{tQbN+f^7kA?Aw_nALSh~+1GF!wy@c_eG?5xB`SY_`0{>Sc>*xc=n=gq$Pe??T?<1Cx z)O|;xJ)I6S>QyO3qCAm*eO8U>EVgl4DuwGqY)_s^g3n3_waiu1TCCmPjXi*kl_iQs zf>hEL!&XA;lKf7kVJns}F$2z^fIWNyKJ<6BlPl9}@Vmj_dmt&^jqk+9E8IO4Oysg| zf@ve2JfZ)o{{2|LoS27a&DRkDHHj86N!XJ>4ZIwTTCo>azqay^I*R;&`^Z0+?1sh?f-H4Hc5R5`DLNy)6;JU9|BI&35@ z^cvp+@9|ZXs~@!UTeq>+u415v8vE_=tH=2puRvg@)C*KE#Q)k0-&-$+dXB5KNX?() zk&kIAQo4!p_7u}@7#4iv7sZa^`WD1BJzWh-n=Zy@H+8douSjPsCb|16dj%nQ2<;_? zoJ;)0eAUjUZe#u#R%ds4F@OKnl2N=ny94kY4*vQkX1lQg`SY+E3u0iEkcXi4@`e^t zuLpeWhcx}J4AG5x*^NUY^;Ifc$N>e4`&;0IgCyuebwoYXH?0%(a3F5&g{u@%#dxME1%bHCK{8-+Q z!(2{1ofi0^C~pprh*7;FlfQ8*i*gONR{S8*5=dUClOx6|haFzH^vyJzEDs@9jS}Ua z9nFM}dQJybjA#}awen1ndg7-5zMtp3o>6}Q?Kf>+LC-`DU*%8>kt$v-{cHygEBs|k zN{f~n<;0V^6Y_V_&ILK~8s)^>hrj<6Sc^1(5sN`>$fO&=b0kcoW&{^Vc&Vh5Ctzc7jw~;B$s5o!xb(7B5~c|OzJ`Fo1#2O6 zyO+@aW=EkXN}=0Z2t5GsadcsI7H#35T3`Ntf3;1>3NZkovTlG#eU-oRQ}Ha4SD#he z@rVTGk6%%yuSZoM;u#6d{q-m|?*Lz!z*4maJRSv~aAeVk_yI()Tmd%u0FO#!5#WX2 zmgX7e7Li{g(tsq7;7%{y3DFgj)3rzZ%%baq!{a^we@zuH@EBwt|B~V8|&iswma<@LW~}GMnZT-l3bbAlfR&Y44S>}*~K7NTvV&PI`L;- zcbONycc=Sfr>jZapjZ@K7sCSNxp`nkTOsk-q3Tt%`pA0VLwht&l!jhVc`0I=X8@7kM1CP7(wdIqP^zH=A$T{e z$bbDD#9A>&F6y)q`u~+jz8)g#|J);=D~7hN43+}CaM#c~F#H{y<^Low+=r4ZtCwc! zkVxTc>!Kk~^MmbK+mP^aZgC%$W=iEg$F#tmvfMi|c#Y_x9>ArSyCVaNZKidlCo5p=yR8-a6Ct@QsZ-aE zA+8D`N`>gWLx{IUSDUpQ81wRG-x3Y^%ZC0ugm_G}b3qPKqa31Ft{Fo7I`rBh#Mzn zEc_==p?ilA_X(jVN}>NpAw=j{F#z5n#3ER2N|_yhX*k~h2%7mCG4w(H-DI~cCp>^= z#b$GL=hzmm_vWGI;bBp(V~2r4)egdR@1aMF<-b{tt?6;SXrb97C%50T|L6*PdaM&g z|D^|9P9Craemf-#7b7sq)~vKQF(fx?e3cY(*h3|<^zLu831quO4i%}d(vqt= zQ12Q~ChP&C{z6%Qj!1*lgVH~@Ddu(Qc9*wDd>80qbGS)DZs7s16p{jqLPv2tx~!ym zi3nduhs_FNLT+k!;I|XoDJUY@>wsTBLlLD&t`nR1yooH7@0<;f?e!9PD6sxsmcoDj zoTW40D4zTUOHvI!`3u%H_E(QM6@8Sy!v+|xldr?Ua)f`2dU%m-`9V9WO6K{Qp#q?hw^vyc4 zKM9}A7#qo_X{TEiU#$9M`qvpb7oe8)=QJI3DN#>1Tl z$9S;uaLsmsPaO)G<_a~k0?npo!hJ-AX1u);FH@8wo*+DFFu|~GEa=K3ua2>U+<1b~ zoD2cGvauFijLxUCAi=F9A-UV`7@K@G-UQfmg=;p*Su`jQ&^hw(Ksb~`Yb2TL z1f2aBktmMIX_#>-vs+8f^XM2DRuF{NNyM@JM8+)%{G~y-vgQ8lB#>Ye$RZmi9$wZq z11g3nFQjrf)FdQ^(WWS0h~+NqIn{JGFky(Oku7^-eRN8fza(c7);G~mWs}5VgO`S6 z5_Cv3@K-?w=+jnWGyTM7`p`b?%9aYw91~K+4HTsva*naR35K3v6{{h~1lnY9Oq2)m ziloj=>L&0V6UoLIMb$)iVgi-t#f#KfT_Vcy$4Rh#W$tSFg70j|+u?s54j5c|DT&M~ zUjk)z_@7-2f8d%8qPIi3M5dNgQ-_jk1VCieo$0{dL#iXM;W37QN6P7r~Ak#(pv)Mct4wFqcX`4`Y*-` zy9n2eWetL3qs=)=ZD%PRbwZ?g(p)?dfgYfcdd5HoF6&qS(8uupsktFALENvNZYDe8 zE6(mh=YpBoNEts65q8abbZ*HOj$R3l zvn>NqhW!L(lheRaMq>_V_e8m4^KnYcXw0lbh>r4OSnh)FSsx{YQZj)K_cH?8=5bWe zXQ7SsIfPenNu==Ea|zQ|ec?%{gnyU!rIuFwOTuzCR9ukoUPxUq9V*Vt0vc>bg&d>w z6jAP|_)XqVl<=<-&Xn*k5>Auw&l1j&FzrVf=~)tPl=lmouzg_QCs~k32%AVb9A#&y z+!jjZWv3+^Kybw=S^lFer_Do0*$)y{!&Qkru+YXT=ToGy$y? z)8sxJVbld05>#r`(ri*v#j;%4F^C0t<+R0z^HbnGF3WRo5}CpRg728+;}W^LrBtv4 zuZoso0R{ z7^@|JE<4c!bo_RG#k|Dj^F3PiqPy-f&*|c_m@0dmPqzQz#-v|a%C>oTeP_H1R z)}Je&`xfIQY3|p+u5T@05rnUB4Q*fv>QX+pfkk&B3!_);78RZS6Zq2&Y=pX(fBi5U z-miPKV5}B8j2FBRCD8E4F%Ee=&qWV&1UOTeg4@=_Yf9O5?3Zu&9+NF*8^7ThPq4$< zC3phpT%AeNr3$6cn)VaPmprRe!AKYc{au zObh27o&gLu8=uzmG0(76^;dpp9ZP8D;_dp{Jo@8`QlQzlk+t{ zTnhg1ps)F?GDuH;#m|~D?FaD#l`M+29>mX8vShY9j6`Ksdh}yHs|rM!+KES1gIpd9;~T3z)JrfS`+58}7`0^s`Fu=&JH)qd0|`ERh^M{+&E4`P zpAV_~STp z{C*%<3uD)#BVW9OO=J&+@clbH$8O3;yu)k2`=SuO@-@#JLqFnsUW1wuhxqc>0pY!a z{M_s8gqyX?AM$f=cuvw^KjaxZfwe9nG;J*PIrd@go}DaNWha7Zj#@Wf{g9{bf)=KB z;AeM%RBD6y{5nq-R(@E!rH;Xa<&0pS`ZnBgKLBn2Az%D9+rsR@JpLVE!g?yk&VIn> zzXPh+9%%gnMEFAAMh*huw5+nAm8;a+tn=c%^&c!dsroVGl*xr2W^C-Z9yya z@CSVPd(dJ07U1l?ERsFmm9N~(POv3G^kB`+&F~NSmVF?if*>CMz9%=yz|H&6=%65; z`~lE*bJGECd;nd4wOwuehb-TRJ<)|8@3~u_@jl;E45{^#t8ur|UJTjxA+8cY?Yc?xg)1&ax3g6I{( zs4Ix2m+uv+FZ_=M+$nt-<5+9mj5r~BWsyhe+v~nLBxp?uS&t#y#!P7kB1i;HAJRiQ-^*w0PV9=s;(UANT=At|^tT{Sox1F%?cV!_nbWsXP_a6RAA^6g0dhlJ7bNW=m_$uVC76 zif5c=@wTtw163Z|MR0tE4&OvI@#UvsLhU`pubhTi@{vE^at7#EoZ@HC0zH0;uRIHk zua@xgQ+&)%06Qc+^Ay$(=GQf{IV@Ke!Z(V}HL|We>~}VleaCqI@9Z$U(Vu5rgf3p{ z%eP#F7S8nL@t1(}lYRM^OJHvP(U(_Vg8Lu$<-wQ15bx{DhhJvH{da?F=BvEkR6EaK zYgE~QD4yR&+rno5$UC&v^4a1a`TVxpEH>sxzGbjadTl^EZIQ})#+e(P!R*Gk+KuhC zSt`5SO~7sAcvMF%l^y;k}L8#T~W7jNNxb?ZiNBu`m0sE8iaiG~YVO zQ$w{VfNLj)YP+<~qhK?}3cN7HOM$f#-lU0W^m1VCfB@T45FU`AMRfB20Q?+z;hn_V zfeG5A_Uw@pe0hRTRO<-1hvi%Nn>T1nn0^9IZTLi~PxIWq+MvKpui6u%`IXIDGP~H_ z+%jQI?6DLS5jB8B=0T+Cs{ntJ=u^D}x_W@$ovJ0UPm;{2DZyRVc@d39E#AFNyUyoU zcq@me^za$L#-HFDzk|WL=>+L@+6mI@9ukf|L3-Q^Pumm)b`LYqM2$AXKp#<3nw9@$nny%7>cy@x6Vu&jHVG`9STj8#Qyp^1j+r0K1m= z)CNMj71AImKLY77NISX)Y9o=ZE2xM4!)CfD_SZmwzRC!cy8A=F_yjc@@E>)>X%Udt zLfRYBBal81sb6uVwiD8X;y7(G(9ed{fOH9@i=g~jNLNDo9^l;o<=;b^1*zYPIPDCS z_gWFDsaOx-=dm87^^iWhA_#uV==cbYt7mKP&C*8rXbPZ%vUbY!V*5pY<`&JRwR#pl z0zO&`>XheopYtb2YwvYZp18WVvHZ?4+H^Mj7~elei)XW=_0{01i6F2^EW4JJ$ce}EtZWu%2!U(qO?ks?Q{*kI6g`9Z`%pBP?6FL zPCc6+vjQ7o!^4k(TfAVAWsX@u*nDWWRtadTAY>!B}cGi=>?T{oUD!38i@PJCtHAx4Pq+B57}(0<^P$$ZuK~AeS?s$AzH72}9sk>{ z+9<6AZZ+KseMjUGQ?=3RTm0T>T6Xv|-@;ZqSsrMn#@$RQR+y^Yt3D_2Sn;=f|1@ok zRs-eH`C6{%(UN@NBLKu&1tsd;{BpjQ&2qlwW2QIDuxHy)7$^_%Me=v1YXP9_Urg6} zX^liLh({D?d4krG0X8OMYbI#lss_Ghrk25q8~Fa2U^$mG@ZecmCA+PG@0tbhWWb*dOVzjro-rHN zpfL?Re>Q0ALH^8atsm>&gD;<}O;kVSJ?Ch>SQofE2dMT9=gIT5wJf-SN8JH%rv`p@ z9_YMJ1K)B7w0_|T?@**IXD5#k{LK-5wg|}h3##fX{NB0RBsc*gh(3En5LI{cp7S)$ z%8n4t4NwPZJ}PPaa-wkel?7)|Y96%OP@jM4P5L0PUwo zv@bO6g;I^b_ZSp?4w;0zslrSN&^dycM!*Sq1~R(=2i8nL{tO6WW+C9zVWu8%&SRz&aC&Wo zi~=~bF=GduXE9R@I0rG412`R@g-i{!H3Boc0mp?IJCxL7rV(&{!b}d(NhpP>Uk^M_ z!b}R#c@Q%S;M8KK0BU_-3MR4?a5|PjMgg3Dn5hTK(=n3*I1h>plx)RJ3G#!P0-)1` zncaYsP!5@5z{$o;1K=#dOby^XiykmAk6HB%xugQL#;Obui>W)!$phnWD#e2G7XUFhnW=U&UDOVLgqot>;@Tb#f<$E zaF4bEr$Da>3vxcy%p*|H02tX-SO+kA)c}2<_AC}G1PoVAPjoNMtu=jFhj8HsKNEYk zAB>x|o__H2F^Cqk#0#y~el^-L-hQ(-LaYBA1oj%t+k-Ddk2gc621=&CrbY5io3)hg zZ@+x?T!hVY@pjIK9_2X2PVmc{!A33QJ)hS?z?cquUh5Us=<$#pCo=59iiy0FKk~fR zPiug^h-Yu`2?`AOytxr177$LJ|KJFo{Ja(nmap#%T9x{K?LS`7o@G8o@G^*5&$?(= zYBY1u@t~MPpEcLV`G3I!MtaZGezZj!p{n^js7CwPT`k~?tE;7Wz*ucOk9=86&zA#{KJL2xx2lZxQ^fZvKG>5saJ6Vf9_>1I=#fJI0jaCYyn+CPpZ{kc=h4t zn90LqZx=&M%wo0S_N&LzVdu#`d@|Va1HAHY+Bvr80N=O`meaT3wEH$#jCLO2XSZn? zKCi$DaMPaK#n#xl>FN|koz{>L=5W+ZR-2sYImK>-Z{;GCDW!arXh&inj zhMXN#K4FKJ2blrCC2)n$<{}HLQDsSM%*rsGuBYJj#)s z)BK~?VBMb+W-iZ7ZJ{M`!RDUaWOxJm?$@>HT7PI|>rT*LZ1<U%r2f!*)&U>0SF&oWT?a=eszFYoy#h@++k=GPZmu&2^tA9?MQZ)(}C0t>v9 z?@|Ci_qH~?(^9YUfAZ1qXdibf@hbkJw&%Or68KFS{@gzBC%^rSpWCM;t7>iB``RF1 zcKv4ikvS`+K|8G1fZt?|hYa*|T}QoYtuBTnDcf@8g!_y}+i&x&85 zMLN z&^<4_=UU?X8(ZS>>;o<7@s@Z%uSK&ZEzx-0HOT$7C7xif(TlzN{s7Pa;MJpq zUt7AyXm+)G;Vt?DyfiO7{JHvT(c9srMYFDr*QwqMZ#vICs7=yB{|58!Ak5Xh%NRd@ z5TSZCve}M>IcwwMXr{-)qnM z@OEdkc6{M!ZG(DWZTmCY$EuoD`;W6)Z7cT*k>k-xoZAaMVC?Lh{G;Er=uWF(RkZq! zDFk)DX>V^ouf5{KUjC?d`$cWI%2s{EuUrCe{(;)Qm$d|yE&hn-UjdKZ@lox{D_Xwh zw~`-G_J{MFVzy0*=>9tQAhL0);>LXY+IkeFcY4a>JEIG3;R5BlK}fRn2&Dj6V1Xy%)`fn!Q4F1 z)@P79oxcmoRZT><^KN2NJD-{AcK&8NpZDMt-UID@CTR@^(Zxw-(6f#mpo@*bNxu$0 zxoqMGwQD-~ELU4>$VBqU0G|;o`~$JKS$j`_PpQhjd!I)I`lPDg^4vh5^DXMU%BOYq z>BZ{b=W9Cq%xBf_*Iw!D)2b!yP`ygv0P@F}c@(Oj$IQo& ziH?EHPPjD!GeNIFC>i6^z7d-=3MB%mEsFN?Ut_YgL}>S$R~UCRa3R|yH@GDrfOfEFrS7@`xV_9H81;2);V!)c)b)8Y%K24k9>>je$xOS}n$**M{-hGtfOl@+{o?@qyyKao9DI2f6 zic+R5?VhPE?AdKIkU?5cWKL&kD%yLTw$kx31-2eX1#d(-jw01 zC)Gv#kofCBH5VB@^bJ@GT1FG;}rKj8MuxDhh*R&<)Qaq zD9|9AlV#uv3M`j_S7d?8z&jM!Ap_4+9(Ml=mB1Th^OlsHLVga&z+wuF`U(Y3Bh_3P zIEMmDWnderHp{>dD4@&0k)$fhfKU37f&S8u42+b1PN2Y6=|=|ck$z5~)=(hT?i1*s z3+|($I8j@uW%rmN3J zDe|=R6m7P4Jl&hu-KRPxY3I}Mp~t6ble#V~zIzIFr`->}tdCou-EAaWT%fI+Hte}h z!q(n<)~Y^i#|ttzKFW#9s2hdX?>2TiHDp)&8pevHBKp5hIQwfDM)Pqnoec_u}y z_l30)J$A*AEoMXy4M5Uh$3GbaEdx4jAcNv-VeMDl|3|)uoU1+5b%g8PPEpH&a6vz+ zwbZTJ5&Hh~w6vo{7pqsFPX*+Do7&9z)E9hCWjA9f^&no7B$sLzn-{c~%HDyDM@6(7 z9Iof7pfA@(=#NFTP}d;+lZZw$bS2z>ffnltzuie!JxXR=(c zAF_?oiBp0*FQPhn<3cU$NR#;Ji>MAMRk|HLR)6e$dgMBFk#>wDL$yYRTQo=O9!GKR z3hk^e8Pd02s_p6-_k1Ve5%Rd zr86_T8Ev3mSfg!He#YqYuh4$!xRv&M$d$Ca$4X~cl6xa(tF$rtinZD{#|#qh+(I;T zo%R6tR$r&BHM}iZuWfPoW&G9JXuY{!JHpt9etjga+71rm&}~s^ebA>3aya4xsPEW7 z*{48J->BW*-L<|r`D%?Om+OktZ`AJ4{%Kc77u`2&Z*-|#kI}ofXs4>IkJn>cv;~fT z&>m5a=NBWlXt#BDrHk*UX{zP$y+fVPt=h_AUTA_{pA~PsRh!f8aFa#xcJ0KjqW;P4 z+Ge_bzV6%FM?=y6=Hj|LXkKVEl;w9)$ptB%Y$#gwAJiG;&eV?Rny!z%lcsXU4IpU} zRVGijO@@IMqNR(IRtX(zlHgO14e*7a!V2vogoyKXnw}+O7>6(DV4N zPJ^3%w$9sHnp)&ttyE=Oj+qqy}!?Jrau(^yC+wm|t%TPVj zF-D!;!UP7*aNB5p4<%wiMGvuA9bMsoj;U;f)UksZdT`oc%G zUPqbowfa#y9A!X%lv?~1yJ;MJjC$L}FVOxyMqS|#U+6@23VC_NJoLW%64jTqJmy6j z{mIgC{fl(f{BbH9$C=O5;Xqf@y0+-wFUY=~T5GXDZK9)D+R+^Sq_)te@pUvip0aDY z4<68eP|!vi?nXVW&F)0%e$}Q*KEqTyY^o26+V)N?n*42lR(ntS(>Fgyea2$S_uS{H zuw|g{dF^&1aOim&ex+pfPqjTpna_%XcKDVWc~IRwcepM5YH>_dQqFzU5xJ0QiGp)L%Xuexp2dV zz72Zn3vF7l^rp6>TOYMgyQjEspEj(EYSq|iC@?N=I`xNY+N>kwta1+x>WllU-l37hxu3fJ6ZdPAr+P168SmS;X46LR6z}HP%Jtrj zYu8-1X8me!)Vp@Yx_I9P(roJUh8N7&-+E8;(h610ufMoo8>j!hMlb(+?OkoLKK=Ka zr)N{&roJmzu3sMC(6@TSign@3SFHH@rdZ#GHJh%{zxcg&Tyb$-YxF+-73YC|#n~?B za)(pULR61_=RoK1L1DUqZftGQsR>J@RI$CLN$fP!GHv|^<&=arB~MxM+rLB2a-egB z!}ArPe>B!P^2pOo!=`0WGan)nZuxMLe&=B4@Bw8@^#_W3$57{pZr4}zZx3{i>K`zp zrbxb%eD+YlIy!{-5$Wi%soDR`S$wS6hEr~VCMYWv1!mp@^X|C4{ZQ9u(pa~-{mX^%LR_C@W^ zncwRa!|Rks^VuLFE|E)c+|CisN{e2g$s2P=^iPf1YuzXeer_fZwSL2&nylmzOYS<@ zrN1WI@F7!QvE(^Q)z}Wwui1vPmg+vrnQX(37TuJ;gWBYP`~lNg2`1Jo!6&qYM>Qz9 zR7#cN%pElTk&7M*v|AB%EA8ek4Q32;j!2$F1{2Mk82>+mdRjqxm4Yu{MLB5RbR97uvv^U>^ki$E26{ zDAYFQAzothh$kCLGktLh39LcI5NF#4ri)#~_7eJlxL0|n-@|^$+l&u*5wX2Q*Ap8V zu+kUXNT5au>9jw^^aJ8kZ2BobatLcoHl%c;Sk<|~4-lVZOJDqf1Pg5rj(Wr) zrr3B1vH8fYDZGu?8iEbMOT_jJc0I~>=s05g4y_|z)Y*SprA-2RqpK0y>+Ex4dxj=I zrW%o%p+&^@HnNG>9zP5AyNKtt%b$FFN&>&>%lLoPV~0qNvpG1L1oi|%#CH8EV(YjyV%Gs4r@U2s_INKO3)VtbqTE3uk0t?d$`$J#lP>%F6CIJ6tY$Uu#k z(qIW0jI|qFLI%oPd=s5qn!~jJY6{XOuA$5n&L$CWJ3$o?{A+ANlnCN{4%MfM%~ z8o5!^p96Gv`lB&aB+e&0(?)r~TF=o6lIy)a&Jok>2A7e6nq$(N6boGZx;U_$w*DYUm!24L+E~r_fDUXhm*%r#%Ga2 zPSoUf2K}ip$@M-mIO`-b7;mV}f6Iy0d4L>*Rcf+&?9-4F>E?xfLLUBeyZNXSwT|dA zJcsku%*swtEV<>ZU-zF(R`RSRZ~jNO^$53lnrlR&e<_m7t@R&NP=lQ#2GtIj0`Zr2 zcKj5STZ3%fMr$4rv7(X4m=O>1S%Uf(D%!$Ae>01b0p@RkRC4QjuG1VPzyGX5$horI z#*>L^o;7K29^!7}9VS97q{}?Zduf(@8~HGwfHmViMQnOE`Pam%;H9KaY(HtF>qVxa z^&j~&(o^ebZ@`C%wA?8pb7#c|n!d{0c=z6)F236HfN9VLR+i@!xitHaS^06M%j`t+ zx8OH_XHpc^hpwT_sbe5Z<+8&|t(5|8gW1%Xs(qFQuaQA=o6W@Hbv`%_?Qej=NmM$`x1vO(%bhK?FZ9^K2;}&Q_j?^ zZVFuqTrz>`gaX#&Ru~7oDvnovMwbeu0R`H#0Z@>3tKO^TyPU zG0VSZDP`>qqdEyr(O;VAyoFX}zdq49sw-yxTy6erF@L^i{yb^?=*>w^zkbYQXI8IF zcIJ{fs~F7{3-q}4cryjXaci!lI6H^y{nlJY=?lPaYbvAkWzI=a;{wqDu3M)h%0Vm0 zGvJ=jIR#t>Rugp7A5A@jr+@?CGBABh%-B?hg1{-@GO$=+i!Z0XLA!uB1zZMJX9(>K zEVMb4@qx?0YPKok%k)LyH2L3r2Z8*p0O!L}+R>MR{bzH1{#?$P^Eu}`Y^GH02u#bR zJc0)}6XE)nKJRE}Z!&ZN7pH;Cz)fK9G9Et+oB_^rPKqiQh!(K-LY_bfI0l>nE&(U#pC zAOYYQaPvBDU%sBR>juuP8#xD!RE-ihL$RB@gWGuL~*$vM4+a~0Tk3)jcnY~>_= z2RA4IH}3-dHqPPia29uS&fdqlpx8=ZG?l=#6R!Jt1P^c+*!2Ln_X2x<#Px|sOirp+ zpRojr3&3^Y_@mrm4Y&zheT>_?9yd7IKI{snpv}G?^9TvxJa8Gf3Y^)={lyBBsr=O* zrwl=dGr-~r?!W`=1C9V!fum2F>9;c=p5h6lfyGa_J`9`!&I0FwV^3RSnJR(YGdzJ3 za0R#t94PYmRp1tI?m2TTZI?jvIqo3!Jm&&%`~|Kr0f%>Uef>qw{+F!dPnAIIC2o-T z8Rz=ToD+LF$6w`~1+M*;>%GZ0xS)W%kLxr4#<>X`{3F-Lfz!Z4vml7*`;K+?>Yf=+ zce3$5cjozb&VJw^aOwkY-vkafxIV}E98vg?8&v+2bF9fZ3tRv${Z*gpcaA$d_A$f* zE&!K-#ou`R=->23oP1GGjWJnoK~B25<)+;2Lm#Ah&M;(>t=K3sf!^TEuQW(P{lzTWgfx|~~eGa$^+yHKM*hpWL#&U=0ah$8b@m{Xa z0ShnJhk-rgmEKHWG}{8BEs2Cr8JO&v!r2Slw9eaABMeOE_WolzhvkJ_+FVxpqNN08 zdcr%CM`+FF?3WjMr31^}H;;1+xBwhF!L)Brro>6yftb%Z4IDUy>!S;R7jkxem2=GG z&Sa_q2GJ0Y5ICK4dJ*S3aP$nW&z;FRf0kq_e^rD{L(pOG5+1?xHO?92bGW_<95|Qj z(~@QVs|-}Zz# zy^_Z-#2M4IM=lWI&CEbF6PyFraQ0lwxo{ol`t_VcDLXr;4eHkf=+cV+n|3)}GP;Fx z2DlC!+sf?&Y0h3cZW zfTQEE8pXs`ab8v4jap(p=B2kr*bv};^uvvV-IllKg`*e=Uf5~Khn_~M`Pg8 zj)2k=fyX$<9_Q@)G4M{#sh@DJ0{fqB>&@OE-x0|4MGM&XTswlH4+4jQ#q-=g2HbjC z>8$+|Wa@UZAT9$pfL({%{gPYY zFmN0=%Q-2^Tp&t=c>-17=n$??0EgXN9~#a%#+aIYKt4LsE-AX83Y-G20B1+@_*LNO z7_Lv)`8-NEZx;|(fkh8@5C)DM$@TFg^+iEy^A(VL#&WwRa9|whf!$uNj{#>nCq-1= z_ohu9R9WB_aBKp%&jHtfa}&9Ji!oKLZxT0%0{bR&eH_?#6xT-soFgZ2uAXFL*`pSM zHUY8w6waOnoFfZ4H%{Xm3UT%?;v71w!$yyuTGA27s;C2(&f$8`xtv|+ac%*ZmU4aO zf;JoJ3)ix?K&CIez<%J`h1}kE5$8B?3Aoi}Gkp=bm^(-Smw=nVzDs!gC~y|Is@O_j zxR!GVLEr>%0k{F|jdFhx;EcuX^UDeth!xyH05}eu2d)EqF6I70CQof2_X#k_1K0Ft zPo<&AcNzB+22KFyfGfaFV9({;e~@!h#JE6YfJ?x2VAo2XfFC#loMNoeNU&0Wc>O3Z650aY9T4g+WHEPKR)T|n%+p6@{gxDM>T zf!oJ`Q^0u}8>=*$Hh~}WI02jiF4$P6FDf>H7sr`$T_aNP}CQlPTgYZ7u>!MT=^yFfyJv_?*T6TitDSKlcLE5!u4w&A@dsNGH?U9 z{5rQ6Z*ngE&dyY6{+eANL}VZ5#Jij;!0z93eHyp|EZ(!(ucS%}*agIK;5={**ju+b zr1(u>&mZ(Li>Rt&f8cf*;1X~h*!6Ebz8^ROoZ_4mc`guD;1;m=kC33X@>>l+{=ad3 z1USx^s=CAkbhZr~_&awP2QCA*fZd<)_kFQNIB*HrbBNm)9CYi1Hq{T*fn6Hc#}1QB<*y>t3_*vRz@i^_P_mx)Q1KhU zW$Sqfr5A&Ee7|H_|H>c@1~p*+VD7*@jI$Rw02~I65gYAKWgrCx;SoH6C~yKe1Dppg z1II==%=WJ`;PLPTat#&G*YPm&8-J)GS~a!v!g#&UfOxCZR99(YkZ z98H3tF@ZY_Oyb-Ec2DN|GH~Q5uJ;_xIhh54+s6$Wz}_ibUk0uLr>1iIz%<76Faj5d z@-fUnc&Br20q2k9`q&K4!g{7i6?vNRxitPWfe^u&+(8^T3!Fcm+lOZBkDTE=Hkmz* z%Ui(FuX26(OwJ`>v4rdMVb0aBF-{8q*-Rkvz^-$+J_TF{j-1QwOTgZxcD;V}66dh- zp-b&D8rgw8`aMhNEV+6aH*;OiISO1|$@S&coEtID-Zh-VoRcEO1)>HVTgx4!fD6EN z;Pg5kzrvV$!8#MrjaJ~mdhQ?$T)m3x#Rks7jhy3lmc4Lk6E|o8`>*EuC~$T&*H?hW zHC*qvu`vo%uH^>h>o}*b=Uf5~-N5w);KYqwpG~#d9EJSfXbY%3s3QOtH*tLhxCN}< zKcsq|)LtGx-DWd=k!uT#^hF-H09*nt16P2nz%|8I`t467s~oqKfterSuJAqZ0DFOb zz(L@U#qC*W6bur;Dc~${0k{HO2X2_$nT58%!2Ju@V_-jU05}931&$j`<*$l71qK=5 z9B=`+3|s}S1Ggm0`d1lnzXE#<><112hk+x&G2jHT(f(8h(qNDSE&*47YryJbaLT{> z^qXROvy$2Vl_2m-UIOZCZ%Q8leH1tb90yJSr+x{u&@>oifV02_;1X~dxB^^Hf}jB` zUgag?0rmn1fP=v5lV++!lj;LwN}xVSrZ@)<3czLHDsUaR$(ic^S9}lMz+PZKa1b~Q z90iUuru0)xAVdZ@2V4Ly16P6Tz)d?F?f=(M0>EBiKX4E@3>*cH+t^4yWfREpCIg%U zE&!K-tH5>OW`~XR1$~jn80S=@qYv{i_5%BXgTP_nC~&;ZR{E*7z(`kQfOEhF;4*L( zxDMP@Y^6`vKKUNg2Yz@70Mqx0n4UgU#5fEb1&&+X9{*EdkO9sC7l6yaRp2^s)8x+h zFJ9++;0C6TBe6a71JkFGm_7^~1*T6ukv)~aYIG?u$N=Yn3&3UIDsUaRDOuLP$^d;m zitPb?M2fK&*bf{8rmsh__)%c`b`&4~Q(%w*&H)#I%fMCOI&kwhF#gjg%vg@yz+PZK za1b~Q90gAN_GF%51`O1Cj{qPH%^7~g) zE}%2-3>f8s3&0iNDsTh13G8~4WlGU%6o+yFP~t3OJwKKG;8uTNR#oH0LPeV#^%)z@?s=RscvuIc|uZj$P&G%A|<`i$c6 zTdV*?3^)!J^%$T~k8WYeZ7vL7K*vCuA1MCG300-?XyV!_bKpX{*0n-;5 zSQVv!v%m%5l8t5hg5DF)9M*xG!1SpEX72{3&jm2OA2`@yBYhF>2#km^;5cv!I0Kvm zE&x~BY^E=&ZGn-#r~%i3Tfp@CdbY>(!h6Q_ntH}z#a8-)UQ*8t;=uHVdZy0+)9dM( zz5rYXu3FrlV%5Q*2`u*W1n70~EI}`@A2OJX7 z-!$~oJ2M>hc?@NszCEBg^hLKm?HhCjf26VF#>=c^8RUQ?W4JyFobhsf7T9w<*9WW@ zr}ihYOfMOp!wuA{VwHh!KG!!Ea~4ZD$HJUbz`nDYo*u=Z+aq#Q36{_2oUvXKr1V+f z*lKQHk8#e)m%GuX;zzEqGaWMdEA0a6LDzD2(TmRHrW|DN=9~kr+{g9)`#A?5u(3R3 z6A#)16iflCR>m$}n7)U?YXob~|z~CCP^>PE{u=I(3?Kf#*k$-^sK$VgQ ztS93YyRGNU6=x=J`z&z4ddyqdN3P)ZG3zl+ORulq;vAZ+SkG#zNUocBBsXxudZba= zM;_$%QQ(;Mc$>1XTTiQ5oD_k#xq~op=^d`G0{ixJedAl4`-pN|U~(p@3`}lXYm(I-gse5mihb6aWW~8#d3^6}oWtL?Sk}MH zKtu^FPTk2P(tPd~2r_U9Z+f}k>p8^i~5PL1MR@Nf}?ASuDroH_E*j+(V3DNJ8(GX$RN%+;98H;Q~65=0qd1aDu-#{j88={ z?Q_#PhevmMI_a+f+yJiiSoX61WrW;Va4?Q@wwJSKJm&;(0XRI7+t((UY>fXh1C7bt zpn4SNfURql_Tj-Dy_4AU%}%3~?Eh9re;BcEOGhs`ZjP4b_}`BNiApDeyv`6Sb(r2? zE+VgVSXNVRcZUZ^wvC;(D~{cu|NcA9 zyAF@uWoC}v<37-Ngns*W=kT$qZKh29elSZqu{QbE>fw5IyVE^dtzlBXRrS(y)7PN8 z=^JQ)cTHdPt53VzIeVyj{kHlI>sQ_FoH10rbzA-ZTz`VZ!_>>Z)o=4+{o}ixa}IBw NC~HE#mx9cC{|gJ8rk?-+ diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 30c25b33a3..5f6adf9b87 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -130,6 +130,7 @@ const ( TestPauseERC20CustodyName = "pause_erc20_custody" TestMigrateERC20CustodyFundsName = "migrate_erc20_custody_funds" TestMigrateTSSName = "migrate_TSS" + TestSolanaWhitelistSPLName = "solana_whitelist_spl" /* V2 smart contract tests @@ -453,6 +454,12 @@ var AllE2ETests = []runner.E2ETest{ }, TestSolanaWithdrawRestricted, ), + runner.NewE2ETest( + TestSolanaWhitelistSPLName, + "whitelist SPL", + []runner.ArgDefinition{}, + TestSolanaWhitelistSPL, + ), /* TON tests */ diff --git a/e2e/e2etests/test_solana_whitelist_spl.go b/e2e/e2etests/test_solana_whitelist_spl.go new file mode 100644 index 0000000000..259657f72b --- /dev/null +++ b/e2e/e2etests/test_solana_whitelist_spl.go @@ -0,0 +1,68 @@ +package e2etests + +import ( + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/txserver" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/chains" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestSolanaWhitelistSPL(r *runner.E2ERunner, _ []string) { + // Deploy a new SPL + r.Logger.Info("Deploying new SPL") + + // load deployer private key + privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String()) + require.NoError(r, err) + + spl := r.DeploySPL(&privkey) + + // check that whitelist entry doesn't exist for this spl + seed := [][]byte{[]byte("whitelist"), spl.PublicKey().Bytes()} + whitelistEntryPDA, _, err := solana.FindProgramAddress(seed, r.GatewayProgram) + require.NoError(r, err) + + whitelistEntryInfo, err := r.SolanaClient.GetAccountInfo(r.Ctx, whitelistEntryPDA) + require.Error(r, err) + require.Nil(r, whitelistEntryInfo) + + // whitelist sol zrc20 + r.Logger.Info("whitelisting spl on new network") + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, crosschaintypes.NewMsgWhitelistERC20( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + spl.PublicKey().String(), + chains.SolanaLocalnet.ChainId, + "TESTSPL", + "TESTSPL", + 6, + 100000, + )) + require.NoError(r, err) + + event, ok := txserver.EventOfType[*crosschaintypes.EventERC20Whitelist](res.Events) + require.True(r, ok, "no EventERC20Whitelist in %s", res.TxHash) + erc20zrc20Addr := event.Zrc20Address + whitelistCCTXIndex := event.WhitelistCctxIndex + + err = r.ZetaTxServer.InitializeLiquidityCaps(erc20zrc20Addr) + require.NoError(r, err) + + // ensure CCTX created + resCCTX, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: whitelistCCTXIndex}) + require.NoError(r, err) + + cctx := resCCTX.CrossChainTx + r.Logger.CCTX(*cctx, "whitelist_cctx") + + // wait for the whitelist cctx to be mined + r.WaitForMinedCCTXFromIndex(whitelistCCTXIndex) + + // check that whitelist entry exists for this spl + whitelistEntryInfo, err = r.SolanaClient.GetAccountInfo(r.Ctx, whitelistEntryPDA) + require.NoError(r, err) + require.NotNil(r, whitelistEntryInfo) +} diff --git a/e2e/runner/setup_solana.go b/e2e/runner/setup_solana.go index b8bb309ba1..73a571b2be 100644 --- a/e2e/runner/setup_solana.go +++ b/e2e/runner/setup_solana.go @@ -49,12 +49,11 @@ func (r *E2ERunner) SetupSolana(deployerPrivateKey string) { accountSlice = append(accountSlice, solana.Meta(privkey.PublicKey()).WRITE().SIGNER()) accountSlice = append(accountSlice, solana.Meta(pdaComputed).WRITE()) accountSlice = append(accountSlice, solana.Meta(solana.SystemProgramID)) - accountSlice = append(accountSlice, solana.Meta(r.GatewayProgram)) inst.ProgID = r.GatewayProgram inst.AccountValues = accountSlice inst.DataBytes, err = borsh.Serialize(solanacontracts.InitializeParams{ - Discriminator: solanacontracts.DiscriminatorInitialize(), + Discriminator: solanacontracts.DiscriminatorInitialize, TssAddress: r.TSSAddress, // #nosec G115 chain id always positive ChainID: uint64(chains.SolanaLocalnet.ChainId), @@ -62,7 +61,7 @@ func (r *E2ERunner) SetupSolana(deployerPrivateKey string) { require.NoError(r, err) // create and sign the transaction - signedTx := r.CreateSignedTransaction([]solana.Instruction{&inst}, privkey) + signedTx := r.CreateSignedTransaction([]solana.Instruction{&inst}, privkey, []solana.PrivateKey{}) // broadcast the transaction and wait for finalization _, out := r.BroadcastTxSync(signedTx) diff --git a/e2e/runner/solana.go b/e2e/runner/solana.go index 4d5e6a9b9d..24ea3c3b2f 100644 --- a/e2e/runner/solana.go +++ b/e2e/runner/solana.go @@ -6,6 +6,8 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" + "github.com/gagliardetto/solana-go/programs/token" "github.com/gagliardetto/solana-go/rpc" "github.com/near/borsh-go" "github.com/stretchr/testify/require" @@ -49,7 +51,7 @@ func (r *E2ERunner) CreateDepositInstruction( var err error inst.DataBytes, err = borsh.Serialize(solanacontract.DepositInstructionParams{ - Discriminator: solanacontract.DiscriminatorDeposit(), + Discriminator: solanacontract.DiscriminatorDeposit, Amount: amount, Memo: append(receiver.Bytes(), data...), }) @@ -62,6 +64,7 @@ func (r *E2ERunner) CreateDepositInstruction( func (r *E2ERunner) CreateSignedTransaction( instructions []solana.Instruction, privateKey solana.PrivateKey, + additionalPrivateKeys []solana.PrivateKey, ) *solana.Transaction { // get a recent blockhash recent, err := r.SolanaClient.GetLatestBlockhash(r.Ctx, rpc.CommitmentFinalized) @@ -81,6 +84,11 @@ func (r *E2ERunner) CreateSignedTransaction( if privateKey.PublicKey().Equals(key) { return &privateKey } + for _, apk := range additionalPrivateKeys { + if apk.PublicKey().Equals(key) { + return &apk + } + } return nil }, ) @@ -89,6 +97,40 @@ func (r *E2ERunner) CreateSignedTransaction( return tx } +func (r *E2ERunner) DeploySPL(privateKey *solana.PrivateKey) *solana.Wallet { + lamport, err := r.SolanaClient.GetMinimumBalanceForRentExemption(r.Ctx, token.MINT_SIZE, rpc.CommitmentFinalized) + require.NoError(r, err) + + // to deploy new spl token, create account instruction and initialize mint instruction have to be in the same transaction + tokenAccount := solana.NewWallet() + createAccountInstruction := system.NewCreateAccountInstruction( + lamport, + token.MINT_SIZE, + solana.TokenProgramID, + privateKey.PublicKey(), + tokenAccount.PublicKey(), + ).Build() + + initializeMintInstruction := token.NewInitializeMint2Instruction( + 6, + privateKey.PublicKey(), + privateKey.PublicKey(), + tokenAccount.PublicKey(), + ).Build() + + signedTx := r.CreateSignedTransaction( + []solana.Instruction{createAccountInstruction, initializeMintInstruction}, + *privateKey, + []solana.PrivateKey{tokenAccount.PrivateKey}, + ) + + // broadcast the transaction and wait for finalization + _, out := r.BroadcastTxSync(signedTx) + r.Logger.Info("create spl logs: %v", out.Meta.LogMessages) + + return tokenAccount +} + // BroadcastTxSync broadcasts a transaction and waits for it to be finalized func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, *rpc.GetTransactionResult) { // broadcast the transaction @@ -134,7 +176,7 @@ func (r *E2ERunner) SOLDepositAndCall( instruction := r.CreateDepositInstruction(signerPrivKey.PublicKey(), receiver, data, amount.Uint64()) // create and sign the transaction - signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, *signerPrivKey) + signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, *signerPrivKey, []solana.PrivateKey{}) // broadcast the transaction and wait for finalization sig, out := r.BroadcastTxSync(signedTx) diff --git a/go.mod b/go.mod index bb5b93539c..13a35b26bc 100644 --- a/go.mod +++ b/go.mod @@ -336,6 +336,7 @@ require ( github.com/bnb-chain/tss-lib v1.5.0 github.com/showa-93/go-mask v0.6.2 github.com/tonkeeper/tongo v1.9.3 + github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b ) require ( diff --git a/go.sum b/go.sum index 06040e5b57..fec35684fa 100644 --- a/go.sum +++ b/go.sum @@ -2186,6 +2186,8 @@ github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlya github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= +github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= +github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= github.com/gagliardetto/solana-go v1.10.0 h1:lDuHGC+XLxw9j8fCHBZM9tv4trI0PVhev1m9NAMaIdM= github.com/gagliardetto/solana-go v1.10.0/go.mod h1:afBEcIRrDLJst3lvAahTr63m6W2Ns6dajZxe2irF7Jg= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= @@ -4212,6 +4214,8 @@ github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 h1:vck/Fc github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138/go.mod h1:U494OsZTWsU75hqoriZgMdSsgSGP1mUL1jX+wN/Aez8= github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c h1:ZoFxMMZtivRLquXVq1sEVlT45UnTPMO1MSXtc88nDv4= github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w= +github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b h1:w4YVBbWxk9TI+7HM8hTvK66IgOo5XvEFsmH7n6WgW50= +github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b/go.mod h1:DcDY828o773soiU/h0XpC+naxitrIMFVZqEvq/EJxMA= github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901 h1:9whtN5fjYHfk4yXIuAsYP2EHxImwDWDVUOnZJ2pfL3w= github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901/go.mod h1:d2iTC62s9JwKiCMPhcDDXbIZmuzAyJ4lwso0H5QyRbk= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go index 0102e74517..6731a70959 100644 --- a/pkg/chains/chain.go +++ b/pkg/chains/chain.go @@ -96,6 +96,10 @@ func (chain Chain) IsEVMChain() bool { return chain.Vm == Vm_evm } +func (chain Chain) IsSolanaChain() bool { + return chain.Consensus == Consensus_solana_consensus +} + func (chain Chain) IsBitcoinChain() bool { return chain.Consensus == Consensus_bitcoin } diff --git a/pkg/contracts/solana/gateway.go b/pkg/contracts/solana/gateway.go index a8f0c571e5..a3adcf5eae 100644 --- a/pkg/contracts/solana/gateway.go +++ b/pkg/contracts/solana/gateway.go @@ -4,6 +4,7 @@ package solana import ( "github.com/gagliardetto/solana-go" "github.com/pkg/errors" + idlgateway "github.com/zeta-chain/protocol-contracts-solana/go-idl/generated" ) const ( @@ -18,30 +19,20 @@ const ( AccountsNumDeposit = 3 ) -// DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction -func DiscriminatorInitialize() [8]byte { - return [8]byte{175, 175, 109, 31, 13, 152, 155, 237} -} - -// DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction -func DiscriminatorDeposit() [8]byte { - return [8]byte{242, 35, 198, 137, 82, 225, 242, 182} -} - -// DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction -func DiscriminatorDepositSPL() [8]byte { - return [8]byte{86, 172, 212, 121, 63, 233, 96, 144} -} - -// DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction -func DiscriminatorWithdraw() [8]byte { - return [8]byte{183, 18, 70, 156, 148, 109, 161, 34} -} - -// DiscriminatorWithdrawSPL returns the discriminator for Solana gateway 'withdraw_spl_token' instruction -func DiscriminatorWithdrawSPL() [8]byte { - return [8]byte{156, 234, 11, 89, 235, 246, 32} -} +var ( + // DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction + DiscriminatorInitialize = idlgateway.IDLGateway.GetDiscriminator("initialize") + // DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction + DiscriminatorDeposit = idlgateway.IDLGateway.GetDiscriminator("deposit") + // DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction + DiscriminatorDepositSPL = idlgateway.IDLGateway.GetDiscriminator("deposit_spl_token") + // DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction + DiscriminatorWithdraw = idlgateway.IDLGateway.GetDiscriminator("withdraw") + // DiscriminatorWithdrawSPL returns the discriminator for Solana gateway 'withdraw_spl_token' instruction + DiscriminatorWithdrawSPL = idlgateway.IDLGateway.GetDiscriminator("withdraw_spl_token") + // DiscriminatorWhitelist returns the discriminator for Solana gateway 'whitelist_spl_mint' instruction + DiscriminatorWhitelistSplMint = idlgateway.IDLGateway.GetDiscriminator("whitelist_spl_mint") +) // ParseGatewayAddressAndPda parses the gateway id and program derived address from the given string func ParseGatewayIDAndPda(address string) (solana.PublicKey, solana.PublicKey, error) { diff --git a/pkg/contracts/solana/gateway.json b/pkg/contracts/solana/gateway.json index 8747c2ca0f..b42f29779e 100644 --- a/pkg/contracts/solana/gateway.json +++ b/pkg/contracts/solana/gateway.json @@ -27,7 +27,76 @@ }, { "name": "pda", - "writable": true + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "receiver", + "type": { + "array": [ + "u8", + 20 + ] + } + } + ] + }, + { + "name": "deposit_and_call", + "discriminator": [ + 65, + 33, + 186, + 198, + 114, + 223, + 133, + 57 + ], + "accounts": [ + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } }, { "name": "system_program", @@ -40,7 +109,16 @@ "type": "u64" }, { - "name": "memo", + "name": "receiver", + "type": { + "array": [ + "u8", + 20 + ] + } + }, + { + "name": "message", "type": "bytes" } ] @@ -65,7 +143,97 @@ }, { "name": "pda", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "whitelist_entry", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 119, + 104, + 105, + 116, + 101, + 108, + 105, + 115, + 116 + ] + }, + { + "kind": "account", + "path": "mint_account" + } + ] + } + }, + { + "name": "mint_account" + }, + { + "name": "token_program", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "from", + "writable": true + }, + { + "name": "to", + "writable": true + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "receiver", + "type": { + "array": [ + "u8", + 20 + ] + } + } + ] + }, + { + "name": "deposit_spl_token_and_call", + "discriminator": [ + 14, + 181, + 27, + 187, + 171, + 61, + 237, + 147 + ], + "accounts": [ + { + "name": "signer", "writable": true, + "signer": true + }, + { + "name": "pda", "pda": { "seeds": [ { @@ -80,6 +248,34 @@ ] } }, + { + "name": "whitelist_entry", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 119, + 104, + 105, + 116, + 101, + 108, + 105, + 115, + 116 + ] + }, + { + "kind": "account", + "path": "mint_account" + } + ] + } + }, + { + "name": "mint_account" + }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" @@ -99,7 +295,16 @@ "type": "u64" }, { - "name": "memo", + "name": "receiver", + "type": { + "array": [ + "u8", + 20 + ] + } + }, + { + "name": "message", "type": "bytes" } ] @@ -153,6 +358,242 @@ 20 ] } + }, + { + "name": "chain_id", + "type": "u64" + } + ] + }, + { + "name": "initialize_rent_payer", + "discriminator": [ + 225, + 73, + 166, + 180, + 25, + 245, + 183, + 96 + ], + "accounts": [ + { + "name": "rent_payer_pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 114, + 101, + 110, + 116, + 45, + 112, + 97, + 121, + 101, + 114 + ] + } + ] + } + }, + { + "name": "authority", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "set_deposit_paused", + "discriminator": [ + 98, + 179, + 141, + 24, + 246, + 120, + 164, + 143 + ], + "accounts": [ + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "signer", + "writable": true, + "signer": true + } + ], + "args": [ + { + "name": "deposit_paused", + "type": "bool" + } + ] + }, + { + "name": "unwhitelist_spl_mint", + "discriminator": [ + 73, + 142, + 63, + 191, + 233, + 238, + 170, + 104 + ], + "accounts": [ + { + "name": "whitelist_entry", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 119, + 104, + 105, + 116, + 101, + 108, + 105, + 115, + 116 + ] + }, + { + "kind": "account", + "path": "whitelist_candidate" + } + ] + } + }, + { + "name": "whitelist_candidate" + }, + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "authority", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "signature", + "type": { + "array": [ + "u8", + 64 + ] + } + }, + { + "name": "recovery_id", + "type": "u8" + }, + { + "name": "message_hash", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "nonce", + "type": "u64" + } + ] + }, + { + "name": "update_authority", + "discriminator": [ + 32, + 46, + 64, + 28, + 149, + 75, + 243, + 88 + ], + "accounts": [ + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "signer", + "writable": true, + "signer": true + } + ], + "args": [ + { + "name": "new_authority_address", + "type": "pubkey" } ] }, @@ -171,7 +612,20 @@ "accounts": [ { "name": "pda", - "writable": true + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } }, { "name": "signer", @@ -191,6 +645,104 @@ } ] }, + { + "name": "whitelist_spl_mint", + "discriminator": [ + 30, + 110, + 162, + 42, + 208, + 147, + 254, + 219 + ], + "accounts": [ + { + "name": "whitelist_entry", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 119, + 104, + 105, + 116, + 101, + 108, + 105, + 115, + 116 + ] + }, + { + "kind": "account", + "path": "whitelist_candidate" + } + ] + } + }, + { + "name": "whitelist_candidate" + }, + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } + }, + { + "name": "authority", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "signature", + "type": { + "array": [ + "u8", + 64 + ] + } + }, + { + "name": "recovery_id", + "type": "u8" + }, + { + "name": "message_hash", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "nonce", + "type": "u64" + } + ] + }, { "name": "withdraw", "discriminator": [ @@ -211,7 +763,20 @@ }, { "name": "pda", - "writable": true + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 101, + 116, + 97 + ] + } + ] + } }, { "name": "to", @@ -287,19 +852,63 @@ } }, { - "name": "from", + "name": "pda_ata", "writable": true }, { - "name": "to", + "name": "mint_account" + }, + { + "name": "recipient" + }, + { + "name": "recipient_ata", + "docs": [ + "the validation will be done in the instruction processor." + ], "writable": true }, + { + "name": "rent_payer_pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 114, + 101, + 110, + 116, + 45, + 112, + 97, + 121, + 101, + 114 + ] + } + ] + } + }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" } ], "args": [ + { + "name": "decimals", + "type": "u8" + }, { "name": "amount", "type": "u64" @@ -346,6 +955,32 @@ 43, 94 ] + }, + { + "name": "RentPayerPda", + "discriminator": [ + 48, + 247, + 192, + 150, + 46, + 218, + 14, + 121 + ] + }, + { + "name": "WhitelistEntry", + "discriminator": [ + 51, + 70, + 173, + 81, + 219, + 192, + 234, + 62 + ] } ], "errors": [ @@ -388,6 +1023,16 @@ "code": 6007, "name": "MemoLengthTooShort", "msg": "MemoLengthTooShort" + }, + { + "code": 6008, + "name": "DepositPaused", + "msg": "DepositPaused" + }, + { + "code": 6009, + "name": "SPLAtaAndMintAddressMismatch", + "msg": "SPLAtaAndMintAddressMismatch" } ], "types": [ @@ -412,9 +1057,31 @@ { "name": "authority", "type": "pubkey" + }, + { + "name": "chain_id", + "type": "u64" + }, + { + "name": "deposit_paused", + "type": "bool" } ] } + }, + { + "name": "RentPayerPda", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "WhitelistEntry", + "type": { + "kind": "struct", + "fields": [] + } } ] } \ No newline at end of file diff --git a/pkg/contracts/solana/gateway_message.go b/pkg/contracts/solana/gateway_message.go index 021af3cf1f..1c8abaca23 100644 --- a/pkg/contracts/solana/gateway_message.go +++ b/pkg/contracts/solana/gateway_message.go @@ -61,6 +61,8 @@ func (msg *MsgWithdraw) Hash() [32]byte { var message []byte buff := make([]byte, 8) + message = append(message, []byte("withdraw")...) + binary.BigEndian.PutUint64(buff, msg.chainID) message = append(message, buff...) @@ -105,3 +107,103 @@ func (msg *MsgWithdraw) Signer() (common.Address, error) { return RecoverSigner(msgHash[:], msgSig[:]) } + +// MsgWhitelist is the message for the Solana gateway whitelist_spl_mint instruction +type MsgWhitelist struct { + // whitelistCandidate is the SPL token to be whitelisted in gateway program + whitelistCandidate solana.PublicKey + + // whitelistEntry is the entry in gateway program representing whitelisted SPL token + whitelistEntry solana.PublicKey + + // chainID is the chain ID of Solana chain + chainID uint64 + + // Nonce is the nonce for the withdraw/withdraw_spl + nonce uint64 + + // signature is the signature of the message + signature [65]byte +} + +// NewMsgWhitelist returns a new whitelist_spl_mint message +func NewMsgWhitelist( + whitelistCandidate solana.PublicKey, + whitelistEntry solana.PublicKey, + chainID, nonce uint64, +) *MsgWhitelist { + return &MsgWhitelist{ + whitelistCandidate: whitelistCandidate, + whitelistEntry: whitelistEntry, + chainID: chainID, + nonce: nonce, + } +} + +// To returns the recipient address of the message +func (msg *MsgWhitelist) WhitelistCandidate() solana.PublicKey { + return msg.whitelistCandidate +} + +func (msg *MsgWhitelist) WhitelistEntry() solana.PublicKey { + return msg.whitelistEntry +} + +// ChainID returns the chain ID of the message +func (msg *MsgWhitelist) ChainID() uint64 { + return msg.chainID +} + +// Nonce returns the nonce of the message +func (msg *MsgWhitelist) Nonce() uint64 { + return msg.nonce +} + +// Hash packs the whitelist message and computes the hash +func (msg *MsgWhitelist) Hash() [32]byte { + var message []byte + buff := make([]byte, 8) + + message = append(message, []byte("whitelist_spl_mint")...) + + binary.BigEndian.PutUint64(buff, msg.chainID) + message = append(message, buff...) + + message = append(message, msg.whitelistCandidate.Bytes()...) + + binary.BigEndian.PutUint64(buff, msg.nonce) + message = append(message, buff...) + + return crypto.Keccak256Hash(message) +} + +// SetSignature attaches the signature to the message +func (msg *MsgWhitelist) SetSignature(signature [65]byte) *MsgWhitelist { + msg.signature = signature + return msg +} + +// SigRSV returns the full 65-byte [R+S+V] signature +func (msg *MsgWhitelist) SigRSV() [65]byte { + return msg.signature +} + +// SigRS returns the 64-byte [R+S] core part of the signature +func (msg *MsgWhitelist) SigRS() [64]byte { + var sig [64]byte + copy(sig[:], msg.signature[:64]) + return sig +} + +// SigV returns the V part (recovery ID) of the signature +func (msg *MsgWhitelist) SigV() uint8 { + return msg.signature[64] +} + +// Signer returns the signer of the message +func (msg *MsgWhitelist) Signer() (common.Address, error) { + msgHash := msg.Hash() + msgSig := msg.SigRSV() + + return RecoverSigner(msgHash[:], msgSig[:]) +} diff --git a/pkg/contracts/solana/gateway_message_test.go b/pkg/contracts/solana/gateway_message_test.go index 20c4d84ef9..68af93e859 100644 --- a/pkg/contracts/solana/gateway_message_test.go +++ b/pkg/contracts/solana/gateway_message_test.go @@ -20,7 +20,7 @@ func Test_MsgWithdrawHash(t *testing.T) { amount := uint64(1336000) to := solana.MustPublicKeyFromBase58("37yGiHAnLvWZUNVwu9esp74YQFqxU1qHCbABkDvRddUQ") - wantHash := "a20cddb3f888f4064ced892a477101f45469a8c50f783b966d3fec2455887c05" + wantHash := "aa609ef9480303e8d743f6e36fe1bea0cc56b8d27dcbd8220846125c1181b681" wantHashBytes, err := hex.DecodeString(wantHash) require.NoError(t, err) @@ -29,3 +29,21 @@ func Test_MsgWithdrawHash(t *testing.T) { require.True(t, bytes.Equal(hash[:], wantHashBytes)) }) } + +func Test_MsgWhitelistHash(t *testing.T) { + t.Run("should pass for archived inbound, receipt and cctx", func(t *testing.T) { + // #nosec G115 always positive + chainID := uint64(chains.SolanaLocalnet.ChainId) + nonce := uint64(0) + whitelistCandidate := solana.MustPublicKeyFromBase58("37yGiHAnLvWZUNVwu9esp74YQFqxU1qHCbABkDvRddUQ") + whitelistEntry := solana.MustPublicKeyFromBase58("2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s") + + wantHash := "cde8fa3ab24b50320db1c47f30492e789177d28e76208176f0a52b8ed54ce2dd" + wantHashBytes, err := hex.DecodeString(wantHash) + require.NoError(t, err) + + // create new withdraw message + hash := contracts.NewMsgWhitelist(whitelistCandidate, whitelistEntry, chainID, nonce).Hash() + require.True(t, bytes.Equal(hash[:], wantHashBytes)) + }) +} diff --git a/pkg/contracts/solana/instruction.go b/pkg/contracts/solana/instruction.go index f338129c9b..df5db0416b 100644 --- a/pkg/contracts/solana/instruction.go +++ b/pkg/contracts/solana/instruction.go @@ -99,7 +99,7 @@ func ParseInstructionWithdraw(instruction solana.CompiledInstruction) (*Withdraw } // check the discriminator to ensure it's a 'withdraw' instruction - if inst.Discriminator != DiscriminatorWithdraw() { + if inst.Discriminator != DiscriminatorWithdraw { return nil, fmt.Errorf("not a withdraw instruction: %v", inst.Discriminator) } @@ -116,3 +116,60 @@ func RecoverSigner(msgHash []byte, msgSig []byte) (signer common.Address, err er return crypto.PubkeyToAddress(*pubKey), nil } + +var _ OutboundInstruction = (*WhitelistInstructionParams)(nil) + +// WhitelistInstructionParams contains the parameters for a gateway whitelist_spl_mint instruction +type WhitelistInstructionParams struct { + // Discriminator is the unique identifier for the whitelist instruction + Discriminator [8]byte + + // Signature is the ECDSA signature (by TSS) for the whitelist + Signature [64]byte + + // RecoveryID is the recovery ID used to recover the public key from ECDSA signature + RecoveryID uint8 + + // MessageHash is the hash of the message signed by TSS + MessageHash [32]byte + + // Nonce is the nonce for the whitelist + Nonce uint64 +} + +// Signer returns the signer of the signature contained +func (inst *WhitelistInstructionParams) Signer() (signer common.Address, err error) { + var signature [65]byte + copy(signature[:], inst.Signature[:64]) + signature[64] = inst.RecoveryID + + return RecoverSigner(inst.MessageHash[:], signature[:]) +} + +// GatewayNonce returns the nonce of the instruction +func (inst *WhitelistInstructionParams) GatewayNonce() uint64 { + return inst.Nonce +} + +// TokenAmount returns the amount of the instruction +func (inst *WhitelistInstructionParams) TokenAmount() uint64 { + return 0 +} + +// ParseInstructionWhitelist tries to parse the instruction as a 'whitelist_spl_mint'. +// It returns nil if the instruction can't be parsed as a 'whitelist_spl_mint'. +func ParseInstructionWhitelist(instruction solana.CompiledInstruction) (*WhitelistInstructionParams, error) { + // try deserializing instruction as a 'whitelist_spl_mint' + inst := &WhitelistInstructionParams{} + err := borsh.Deserialize(inst, instruction.Data) + if err != nil { + return nil, errors.Wrap(err, "error deserializing instruction") + } + + // check the discriminator to ensure it's a 'whitelist_spl_mint' instruction + if inst.Discriminator != DiscriminatorWhitelistSplMint { + return nil, fmt.Errorf("not a whitelist_spl_mint instruction: %v", inst.Discriminator) + } + + return inst, nil +} diff --git a/proto/zetachain/zetacore/crosschain/tx.proto b/proto/zetachain/zetacore/crosschain/tx.proto index cb76e53d0a..ed8b6b6f59 100644 --- a/proto/zetachain/zetacore/crosschain/tx.proto +++ b/proto/zetachain/zetacore/crosschain/tx.proto @@ -71,6 +71,7 @@ message MsgAddInboundTracker { } message MsgAddInboundTrackerResponse {} +// TODO: https://github.com/zeta-chain/node/issues/3083 message MsgWhitelistERC20 { string creator = 1; string erc20_address = 2; diff --git a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts index f830e00a48..db3e7073ea 100644 --- a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts @@ -186,6 +186,8 @@ export declare class MsgAddInboundTrackerResponse extends Message { diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 4ae98a85b5..197310e16c 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -8,6 +8,7 @@ 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/gagliardetto/solana-go" "github.com/zeta-chain/node/pkg/coin" authoritytypes "github.com/zeta-chain/node/x/authority/types" @@ -31,20 +32,44 @@ func (k msgServer) WhitelistERC20( return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, err.Error()) } - erc20Addr := ethcommon.HexToAddress(msg.Erc20Address) - if erc20Addr == (ethcommon.Address{}) { + chain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain id (%d) not supported", msg.ChainId) + } + + switch { + case chain.IsEVMChain(): + erc20Addr := ethcommon.HexToAddress(msg.Erc20Address) + if erc20Addr == (ethcommon.Address{}) { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidAddress, + "invalid ERC20 contract address (%s)", + msg.Erc20Address, + ) + } + + case chain.IsSolanaChain(): + _, err := solana.PublicKeyFromBase58(msg.Erc20Address) + if err != nil { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidAddress, + "invalid solana contract address (%s)", + msg.Erc20Address, + ) + } + + default: return nil, errorsmod.Wrapf( - sdkerrors.ErrInvalidAddress, - "invalid ERC20 contract address (%s)", - msg.Erc20Address, + sdkerrors.ErrInvalidChainID, + "whitelist for chain id (%d) not supported", + msg.ChainId, ) } - // check if the erc20 is already whitelisted + // check if the asset is already whitelisted foreignCoins := k.fungibleKeeper.GetAllForeignCoins(ctx) for _, fCoin := range foreignCoins { - assetAddr := ethcommon.HexToAddress(fCoin.Asset) - if assetAddr == erc20Addr && fCoin.ForeignChainId == msg.ChainId { + if fCoin.Asset == msg.Erc20Address && fCoin.ForeignChainId == msg.ChainId { return nil, errorsmod.Wrapf( fungibletypes.ErrForeignCoinAlreadyExist, "ERC20 contract address (%s) already whitelisted on chain (%d)", @@ -59,11 +84,6 @@ func (k msgServer) WhitelistERC20( return nil, errorsmod.Wrapf(types.ErrCannotFindTSSKeys, "Cannot create new admin cmd of type whitelistERC20") } - chain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ChainId) - if !found { - return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain id (%d) not supported", msg.ChainId) - } - // use a temporary context for the zrc20 deployment tmpCtx, commit := ctx.CacheContext() diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20_test.go b/x/crosschain/keeper/msg_server_whitelist_erc20_test.go index 3eb18b9931..c82261bd05 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20_test.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20_test.go @@ -18,81 +18,156 @@ import ( ) func TestKeeper_WhitelistERC20(t *testing.T) { - t.Run("can deploy and whitelist an erc20", func(t *testing.T) { - k, ctx, sdkk, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + tests := []struct { + name string + tokenAddress string + secondTokenAddress string + chainID int64 + }{ + { + name: "can deploy and whitelist an erc20", + tokenAddress: sample.EthAddress().Hex(), + secondTokenAddress: sample.EthAddress().Hex(), + chainID: getValidEthChainID(), + }, + { + name: "can deploy and whitelist a spl", + tokenAddress: sample.SolanaAddress(t), + secondTokenAddress: sample.SolanaAddress(t), + chainID: getValidSolanaChainID(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k, ctx, sdkk, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := crosschainkeeper.NewMsgServerImpl(*k) + k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) + + setSupportedChain(ctx, zk, tt.chainID) + + admin := sample.AccAddress() + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + + deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper) + setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, tt.chainID, "foobar", "FOOBAR") + k.GetObserverKeeper().SetTssAndUpdateNonce(ctx, sample.Tss()) + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: tt.chainID, + MedianIndex: 0, + Prices: []uint64{1}, + }) + + msg := types.MsgWhitelistERC20{ + Creator: admin, + Erc20Address: tt.tokenAddress, + ChainId: tt.chainID, + Name: "foo", + Symbol: "FOO", + Decimals: 18, + GasLimit: 100000, + } + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + res, err := msgServer.WhitelistERC20(ctx, &msg) + require.NoError(t, err) + require.NotNil(t, res) + zrc20 := res.Zrc20Address + cctxIndex := res.CctxIndex + + // check zrc20 and cctx created + assertContractDeployment(t, sdkk.EvmKeeper, ctx, ethcommon.HexToAddress(zrc20)) + fc, found := zk.FungibleKeeper.GetForeignCoins(ctx, zrc20) + require.True(t, found) + require.EqualValues(t, "foo", fc.Name) + require.EqualValues(t, tt.tokenAddress, fc.Asset) + cctx, found := k.GetCrossChainTx(ctx, cctxIndex) + require.True(t, found) + require.EqualValues( + t, + fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, tt.tokenAddress), + cctx.RelayedMessage, + ) + + // check gas limit is set + gasLimit, err := zk.FungibleKeeper.QueryGasLimit(ctx, ethcommon.HexToAddress(zrc20)) + require.NoError(t, err) + require.Equal(t, uint64(100000), gasLimit.Uint64()) + + msgNew := types.MsgWhitelistERC20{ + Creator: admin, + Erc20Address: tt.secondTokenAddress, + ChainId: tt.chainID, + Name: "bar", + Symbol: "BAR", + Decimals: 18, + GasLimit: 100000, + } + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msgNew, nil) + + // Ensure that whitelist a new erc20 create a cctx with a different index + res, err = msgServer.WhitelistERC20(ctx, &msgNew) + require.NoError(t, err) + require.NotNil(t, res) + require.NotEqual(t, cctxIndex, res.CctxIndex) + }) + } + + t.Run("should fail if not authorized", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseAuthorityMock: true, }) msgServer := crosschainkeeper.NewMsgServerImpl(*k) k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) - chainID := getValidEthChainID() - setSupportedChain(ctx, zk, chainID) - admin := sample.AccAddress() - erc20Address := sample.EthAddress().Hex() authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) - deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper) - setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, chainID, "foobar", "FOOBAR") - k.GetObserverKeeper().SetTssAndUpdateNonce(ctx, sample.Tss()) - k.SetGasPrice(ctx, types.GasPrice{ - ChainId: chainID, - MedianIndex: 0, - Prices: []uint64{1}, - }) - msg := types.MsgWhitelistERC20{ Creator: admin, - Erc20Address: erc20Address, - ChainId: chainID, + Erc20Address: sample.EthAddress().Hex(), + ChainId: getValidEthChainID(), Name: "foo", Symbol: "FOO", Decimals: 18, GasLimit: 100000, } - keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) - res, err := msgServer.WhitelistERC20(ctx, &msg) - require.NoError(t, err) - require.NotNil(t, res) - zrc20 := res.Zrc20Address - cctxIndex := res.CctxIndex - - // check zrc20 and cctx created - assertContractDeployment(t, sdkk.EvmKeeper, ctx, ethcommon.HexToAddress(zrc20)) - fc, found := zk.FungibleKeeper.GetForeignCoins(ctx, zrc20) - require.True(t, found) - require.EqualValues(t, "foo", fc.Name) - require.EqualValues(t, erc20Address, fc.Asset) - cctx, found := k.GetCrossChainTx(ctx, cctxIndex) - require.True(t, found) - require.EqualValues(t, fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, erc20Address), cctx.RelayedMessage) - - // check gas limit is set - gasLimit, err := zk.FungibleKeeper.QueryGasLimit(ctx, ethcommon.HexToAddress(zrc20)) - require.NoError(t, err) - require.Equal(t, uint64(100000), gasLimit.Uint64()) - - msgNew := types.MsgWhitelistERC20{ + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, authoritytypes.ErrUnauthorized) + _, err := msgServer.WhitelistERC20(ctx, &msg) + require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) + }) + + t.Run("should fail if invalid erc20 address", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := crosschainkeeper.NewMsgServerImpl(*k) + k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) + + admin := sample.AccAddress() + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + + msg := types.MsgWhitelistERC20{ Creator: admin, - Erc20Address: sample.EthAddress().Hex(), - ChainId: chainID, - Name: "bar", - Symbol: "BAR", + Erc20Address: "invalid", + ChainId: getValidEthChainID(), + Name: "foo", + Symbol: "FOO", Decimals: 18, GasLimit: 100000, } - keepertest.MockCheckAuthorization(&authorityMock.Mock, &msgNew, nil) + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) - // Ensure that whitelist a new erc20 create a cctx with a different index - res, err = msgServer.WhitelistERC20(ctx, &msgNew) - require.NoError(t, err) - require.NotNil(t, res) - require.NotEqual(t, cctxIndex, res.CctxIndex) + _, err := msgServer.WhitelistERC20(ctx, &msg) + require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) }) - t.Run("should fail if not authorized", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + t.Run("should fail if invalid spl address", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseAuthorityMock: true, }) @@ -102,22 +177,26 @@ func TestKeeper_WhitelistERC20(t *testing.T) { admin := sample.AccAddress() authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chainID := getValidSolanaChainID() + setSupportedChain(ctx, zk, chainID) + msg := types.MsgWhitelistERC20{ Creator: admin, - Erc20Address: sample.EthAddress().Hex(), - ChainId: getValidEthChainID(), + Erc20Address: "invalid", + ChainId: chainID, Name: "foo", Symbol: "FOO", Decimals: 18, GasLimit: 100000, } - keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, authoritytypes.ErrUnauthorized) + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + _, err := msgServer.WhitelistERC20(ctx, &msg) - require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) + require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) }) - t.Run("should fail if invalid erc20 address", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + t.Run("should fail if whitelisting not supported for chain", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseAuthorityMock: true, }) @@ -127,10 +206,13 @@ func TestKeeper_WhitelistERC20(t *testing.T) { admin := sample.AccAddress() authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chainID := getValidBtcChainID() + setSupportedChain(ctx, zk, chainID) + msg := types.MsgWhitelistERC20{ Creator: admin, Erc20Address: "invalid", - ChainId: getValidEthChainID(), + ChainId: chainID, Name: "foo", Symbol: "FOO", Decimals: 18, @@ -139,7 +221,7 @@ func TestKeeper_WhitelistERC20(t *testing.T) { keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) _, err := msgServer.WhitelistERC20(ctx, &msg) - require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) + require.ErrorIs(t, err, sdkerrors.ErrInvalidChainID) }) t.Run("should fail if foreign coin already exists for the asset", func(t *testing.T) { diff --git a/x/crosschain/types/message_whitelist_erc20.go b/x/crosschain/types/message_whitelist_erc20.go index 3267581662..d27492d7a5 100644 --- a/x/crosschain/types/message_whitelist_erc20.go +++ b/x/crosschain/types/message_whitelist_erc20.go @@ -4,7 +4,6 @@ import ( cosmoserrors "cosmossdk.io/errors" 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/node/x/fungible/types" ) @@ -53,9 +52,8 @@ func (msg *MsgWhitelistERC20) ValidateBasic() error { if err != nil { return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } - // check if the system contract address is valid - if ethcommon.HexToAddress(msg.Erc20Address) == (ethcommon.Address{}) { - return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid ERC20 contract address (%s)", msg.Erc20Address) + if msg.Erc20Address == "" { + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "empty asset address") } if msg.Decimals > 128 { return cosmoserrors.Wrapf(types.ErrInvalidDecimals, "invalid decimals (%d)", msg.Decimals) diff --git a/x/crosschain/types/message_whitelist_erc20_test.go b/x/crosschain/types/message_whitelist_erc20_test.go index 8219140fd9..d5bf845272 100644 --- a/x/crosschain/types/message_whitelist_erc20_test.go +++ b/x/crosschain/types/message_whitelist_erc20_test.go @@ -32,10 +32,10 @@ func TestMsgWhitelistERC20_ValidateBasic(t *testing.T) { error: true, }, { - name: "invalid erc20", + name: "invalid asset", msg: types.NewMsgWhitelistERC20( sample.AccAddress(), - "0x0", + "", 1, "name", "symbol", diff --git a/x/crosschain/types/tx.pb.go b/x/crosschain/types/tx.pb.go index ffe5c8cec7..52facc862d 100644 --- a/x/crosschain/types/tx.pb.go +++ b/x/crosschain/types/tx.pb.go @@ -337,6 +337,7 @@ func (m *MsgAddInboundTrackerResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgAddInboundTrackerResponse proto.InternalMessageInfo +// TODO: https://github.com/zeta-chain/node/issues/3083 type MsgWhitelistERC20 struct { Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` Erc20Address string `protobuf:"bytes,2,opt,name=erc20_address,json=erc20Address,proto3" json:"erc20_address,omitempty"` diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index 1441150ada..4c93d95470 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -281,7 +281,7 @@ func (ob *Observer) ParseInboundAsDeposit( } // check if the instruction is a deposit or not - if inst.Discriminator != solanacontracts.DiscriminatorDeposit() { + if inst.Discriminator != solanacontracts.DiscriminatorDeposit { return nil, nil } diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go index e185b1a27d..60bd70bec7 100644 --- a/zetaclient/chains/solana/observer/outbound.go +++ b/zetaclient/chains/solana/observer/outbound.go @@ -157,6 +157,7 @@ func (ob *Observer) VoteOutboundIfConfirmed(ctx context.Context, cctx *crosschai // the amount and status of the outbound outboundAmount := new(big.Int).SetUint64(inst.TokenAmount()) + // status was already verified as successful in CheckFinalizedTx outboundStatus := chains.ReceiveStatus_success @@ -295,6 +296,7 @@ func (ob *Observer) CheckFinalizedTx( logger.Error().Err(err).Msg("ParseGatewayInstruction error") return nil, false } + txNonce := inst.GatewayNonce() // recover ECDSA signer from instruction @@ -352,6 +354,8 @@ func ParseGatewayInstruction( switch coinType { case coin.CoinType_Gas: return contracts.ParseInstructionWithdraw(instruction) + case coin.CoinType_Cmd: + return contracts.ParseInstructionWhitelist(instruction) default: return nil, fmt.Errorf("unsupported outbound coin type %s", coinType) } diff --git a/zetaclient/chains/solana/observer/outbound_test.go b/zetaclient/chains/solana/observer/outbound_test.go index 5cb2b80a5c..73af8da573 100644 --- a/zetaclient/chains/solana/observer/outbound_test.go +++ b/zetaclient/chains/solana/observer/outbound_test.go @@ -35,6 +35,9 @@ const ( // tssAddressTest is the TSS address for testing tssAddressTest = "0x05C7dBdd1954D59c9afaB848dA7d8DD3F35e69Cd" + + // whitelistTxTest is local devnet tx result for testing + whitelistTxTest = "phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY" ) // createTestObserver creates a test observer for testing @@ -294,3 +297,63 @@ func Test_ParseInstructionWithdraw(t *testing.T) { require.Nil(t, inst) }) } + +func Test_ParseInstructionWhitelist(t *testing.T) { + // the test chain and transaction hash + chain := chains.SolanaDevnet + txHash := whitelistTxTest + txAmount := uint64(0) + + t.Run("should parse instruction whitelist", func(t *testing.T) { + // tss address used in local devnet + tssAddress := "0x7E8c7bAcd3c6220DDC35A4EA1141BE14F2e1dFEB" + // load and unmarshal archived transaction + txResult := testutils.LoadSolanaOutboundTxResult(t, TestDataDir, chain.ChainId, txHash) + tx, err := txResult.Transaction.GetTransaction() + require.NoError(t, err) + + instruction := tx.Message.Instructions[0] + inst, err := contracts.ParseInstructionWhitelist(instruction) + require.NoError(t, err) + + // check sender, nonce and amount + sender, err := inst.Signer() + require.NoError(t, err) + require.Equal(t, tssAddress, sender.String()) + require.EqualValues(t, inst.GatewayNonce(), 3) + require.EqualValues(t, inst.TokenAmount(), txAmount) + }) + + t.Run("should return error on invalid instruction data", func(t *testing.T) { + // load and unmarshal archived transaction + txResult := testutils.LoadSolanaOutboundTxResult(t, TestDataDir, chain.ChainId, txHash) + txFake, err := txResult.Transaction.GetTransaction() + require.NoError(t, err) + + // set invalid instruction data + instruction := txFake.Message.Instructions[0] + instruction.Data = []byte("invalid instruction data") + + inst, err := contracts.ParseInstructionWhitelist(instruction) + require.ErrorContains(t, err, "error deserializing instruction") + require.Nil(t, inst) + }) + + t.Run("should return error on discriminator mismatch", func(t *testing.T) { + // load and unmarshal archived transaction + txResult := testutils.LoadSolanaOutboundTxResult(t, TestDataDir, chain.ChainId, txHash) + txFake, err := txResult.Transaction.GetTransaction() + require.NoError(t, err) + + // overwrite discriminator (first 8 bytes) + instruction := txFake.Message.Instructions[0] + fakeDiscriminator := "b712469c946da12100980d0000000000" + fakeDiscriminatorBytes, err := hex.DecodeString(fakeDiscriminator) + require.NoError(t, err) + copy(instruction.Data, fakeDiscriminatorBytes) + + inst, err := contracts.ParseInstructionWhitelist(instruction) + require.ErrorContains(t, err, "not a whitelist_spl_mint instruction") + require.Nil(t, inst) + }) +} diff --git a/zetaclient/chains/solana/signer/signer.go b/zetaclient/chains/solana/signer/signer.go index 0d762d9006..8e180f8c7f 100644 --- a/zetaclient/chains/solana/signer/signer.go +++ b/zetaclient/chains/solana/signer/signer.go @@ -2,11 +2,14 @@ package signer import ( "context" + "fmt" + "strings" "cosmossdk.io/errors" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" + "github.com/rs/zerolog" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" @@ -121,12 +124,71 @@ func (signer *Signer) TryProcessOutbound( chainID := signer.Chain().ChainId nonce := params.TssNonce coinType := cctx.InboundParams.CoinType - if coinType != coin.CoinType_Gas { + + // skip relaying the transaction if this signer hasn't set the relayer key + if !signer.HasRelayerKey() { + logger.Warn().Msgf("TryProcessOutbound: no relayer key configured") + return + } + + var tx *solana.Transaction + + switch coinType { + case coin.CoinType_Cmd: + whitelistTx, err := signer.prepareWhitelistTx(ctx, cctx, height) + if err != nil { + logger.Error().Err(err).Msgf("TryProcessOutbound: Fail to sign whitelist outbound") + return + } + + tx = whitelistTx + + case coin.CoinType_Gas: + withdrawTx, err := signer.prepareWithdrawTx(ctx, cctx, height, logger) + if err != nil { + logger.Error().Err(err).Msgf("TryProcessOutbound: Fail to sign withdraw outbound") + return + } + + tx = withdrawTx + default: + logger.Error(). + Msgf("TryProcessOutbound: can only send SOL to the Solana network") + return + } + + // set relayer balance metrics + signer.SetRelayerBalanceMetrics(ctx) + + // broadcast the signed tx to the Solana network with preflight check + txSig, err := signer.client.SendTransactionWithOpts( + ctx, + tx, + // Commitment "finalized" is too conservative for preflight check and + // it results in repeated broadcast attempts that only 1 will succeed. + // Commitment "processed" will simulate tx against more recent state + // thus fails faster once a tx is already broadcasted and processed by the cluster. + // This reduces the number of "failed" txs due to repeated broadcast attempts. + rpc.TransactionOpts{PreflightCommitment: rpc.CommitmentProcessed}, + ) + if err != nil { logger.Error(). - Msgf("TryProcessOutbound: can only send SOL to the Solana network for chain %d nonce %d", chainID, nonce) + Err(err). + Msgf("TryProcessOutbound: broadcast error") return } + // report the outbound to the outbound tracker + signer.reportToOutboundTracker(ctx, zetacoreClient, chainID, nonce, txSig, logger) +} + +func (signer *Signer) prepareWithdrawTx( + ctx context.Context, + cctx *types.CrossChainTx, + height uint64, + logger zerolog.Logger, +) (*solana.Transaction, error) { + params := cctx.GetCurrentOutboundParam() // compliance check cancelTx := compliance.IsCctxRestricted(cctx) if cancelTx { @@ -134,7 +196,7 @@ func (signer *Signer) TryProcessOutbound( logger, signer.Logger().Compliance, true, - chainID, + signer.Chain().ChainId, cctx.Index, cctx.InboundParams.Sender, params.Receiver, @@ -143,48 +205,55 @@ func (signer *Signer) TryProcessOutbound( } // sign gateway withdraw message by TSS - msg, err := signer.SignMsgWithdraw(ctx, params, height, cancelTx) + msg, err := signer.createAndSignMsgWithdraw(ctx, params, height, cancelTx) if err != nil { - logger.Error().Err(err).Msgf("TryProcessOutbound: SignMsgWithdraw error for chain %d nonce %d", chainID, nonce) - return + return nil, err } - // skip relaying the transaction if this signer hasn't set the relayer key - if !signer.HasRelayerKey() { - return + // sign the withdraw transaction by relayer key + tx, err := signer.signWithdrawTx(ctx, *msg) + if err != nil { + return nil, err } - // set relayer balance metrics - signer.SetRelayerBalanceMetrics(ctx) + return tx, nil +} - // sign the withdraw transaction by relayer key - tx, err := signer.SignWithdrawTx(ctx, *msg) +func (signer *Signer) prepareWhitelistTx( + ctx context.Context, + cctx *types.CrossChainTx, + height uint64, +) (*solana.Transaction, error) { + params := cctx.GetCurrentOutboundParam() + relayedMsg := strings.Split(cctx.RelayedMessage, ":") + if len(relayedMsg) != 2 { + return nil, fmt.Errorf("TryProcessOutbound: invalid relayed msg") + } + + pk, err := solana.PublicKeyFromBase58(relayedMsg[1]) if err != nil { - logger.Error().Err(err).Msgf("TryProcessOutbound: SignGasWithdraw error for chain %d nonce %d", chainID, nonce) - return + return nil, err } - // broadcast the signed tx to the Solana network with preflight check - txSig, err := signer.client.SendTransactionWithOpts( - ctx, - tx, - // Commitment "finalized" is too conservative for preflight check and - // it results in repeated broadcast attempts that only 1 will succeed. - // Commitment "processed" will simulate tx against more recent state - // thus fails faster once a tx is already broadcasted and processed by the cluster. - // This reduces the number of "failed" txs due to repeated broadcast attempts. - rpc.TransactionOpts{PreflightCommitment: rpc.CommitmentProcessed}, - ) + seed := [][]byte{[]byte("whitelist"), pk.Bytes()} + whitelistEntryPDA, _, err := solana.FindProgramAddress(seed, signer.gatewayID) if err != nil { - signer.Logger(). - Std.Warn(). - Err(err). - Msgf("TryProcessOutbound: broadcast error for chain %d nonce %d", chainID, nonce) - return + return nil, err } - // report the outbound to the outbound tracker - signer.reportToOutboundTracker(ctx, zetacoreClient, chainID, nonce, txSig, logger) + // sign gateway whitelist message by TSS + msg, err := signer.createAndSignMsgWhitelist(ctx, params, height, pk, whitelistEntryPDA) + if err != nil { + return nil, err + } + + // sign the whitelist transaction by relayer key + tx, err := signer.signWhitelistTx(ctx, msg) + if err != nil { + return nil, err + } + + return tx, nil } // SetGatewayAddress sets the gateway address diff --git a/zetaclient/chains/solana/signer/whitelist.go b/zetaclient/chains/solana/signer/whitelist.go new file mode 100644 index 0000000000..73ee769039 --- /dev/null +++ b/zetaclient/chains/solana/signer/whitelist.go @@ -0,0 +1,125 @@ +package signer + +import ( + "context" + + "cosmossdk.io/errors" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/near/borsh-go" + + contracts "github.com/zeta-chain/node/pkg/contracts/solana" + "github.com/zeta-chain/node/x/crosschain/types" +) + +// createAndSignMsgWhitelist creates and signs a whitelist message (for gateway whitelist_spl_mint instruction) with TSS. +func (signer *Signer) createAndSignMsgWhitelist( + ctx context.Context, + params *types.OutboundParams, + height uint64, + whitelistCandidate solana.PublicKey, + whitelistEntry solana.PublicKey, +) (*contracts.MsgWhitelist, error) { + chain := signer.Chain() + // #nosec G115 always positive + chainID := uint64(signer.Chain().ChainId) + nonce := params.TssNonce + + // prepare whitelist msg and compute hash + msg := contracts.NewMsgWhitelist(whitelistCandidate, whitelistEntry, chainID, nonce) + msgHash := msg.Hash() + + // sign the message with TSS to get an ECDSA signature. + // the produced signature is in the [R || S || V] format where V is 0 or 1. + signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId, "") + if err != nil { + return nil, errors.Wrap(err, "Key-sign failed") + } + signer.Logger().Std.Info().Msgf("Key-sign succeed for chain %d nonce %d", chainID, nonce) + + // attach the signature and return + return msg.SetSignature(signature), nil +} + +// signWhitelistTx wraps the whitelist 'msg' into a Solana transaction and signs it with the relayer key. +func (signer *Signer) signWhitelistTx(ctx context.Context, msg *contracts.MsgWhitelist) (*solana.Transaction, error) { + // create whitelist_spl_mint instruction with program call data + var err error + var inst solana.GenericInstruction + inst.DataBytes, err = borsh.Serialize(contracts.WhitelistInstructionParams{ + Discriminator: contracts.DiscriminatorWhitelistSplMint, + Signature: msg.SigRS(), + RecoveryID: msg.SigV(), + MessageHash: msg.Hash(), + Nonce: msg.Nonce(), + }) + if err != nil { + return nil, errors.Wrap(err, "cannot serialize whitelist_spl_mint instruction") + } + + // attach required accounts to the instruction + privkey := signer.relayerKey + attachWhitelistAccounts( + &inst, + privkey.PublicKey(), + signer.pda, + msg.WhitelistCandidate(), + msg.WhitelistEntry(), + signer.gatewayID, + ) + + // get a recent blockhash + recent, err := signer.client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + return nil, errors.Wrap(err, "GetLatestBlockhash error") + } + + // create a transaction that wraps the instruction + tx, err := solana.NewTransaction( + []solana.Instruction{ + // TODO: outbound now uses 5K lamports as the fixed fee, we could explore priority fee and compute budget + // https://github.com/zeta-chain/node/issues/2599 + // programs.ComputeBudgetSetComputeUnitLimit(computeUnitLimit), + // programs.ComputeBudgetSetComputeUnitPrice(computeUnitPrice), + &inst}, + recent.Value.Blockhash, + solana.TransactionPayer(privkey.PublicKey()), + ) + if err != nil { + return nil, errors.Wrap(err, "NewTransaction error") + } + + // relayer signs the transaction + _, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if key.Equals(privkey.PublicKey()) { + return privkey + } + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "signer unable to sign transaction") + } + + return tx, nil +} + +// attachWhitelistAccounts attaches the required accounts for the gateway whitelist instruction. +func attachWhitelistAccounts( + inst *solana.GenericInstruction, + signer solana.PublicKey, + pda solana.PublicKey, + whitelistCandidate solana.PublicKey, + whitelistEntry solana.PublicKey, + gatewayID solana.PublicKey, +) { + // attach required accounts to the instruction + var accountSlice []*solana.AccountMeta + accountSlice = append(accountSlice, solana.Meta(whitelistEntry).WRITE()) + accountSlice = append(accountSlice, solana.Meta(whitelistCandidate)) + accountSlice = append(accountSlice, solana.Meta(pda).WRITE()) + accountSlice = append(accountSlice, solana.Meta(signer).WRITE().SIGNER()) + accountSlice = append(accountSlice, solana.Meta(solana.SystemProgramID)) + inst.ProgID = gatewayID + + inst.AccountValues = accountSlice +} diff --git a/zetaclient/chains/solana/signer/withdraw.go b/zetaclient/chains/solana/signer/withdraw.go index 58411b43bb..51f4cceeea 100644 --- a/zetaclient/chains/solana/signer/withdraw.go +++ b/zetaclient/chains/solana/signer/withdraw.go @@ -13,8 +13,8 @@ import ( "github.com/zeta-chain/node/x/crosschain/types" ) -// SignMsgWithdraw signs a withdraw message (for gateway withdraw/withdraw_spl instruction) with TSS. -func (signer *Signer) SignMsgWithdraw( +// createAndSignMsgWithdraw creates and signs a withdraw message (for gateway withdraw/withdraw_spl instruction) with TSS. +func (signer *Signer) createAndSignMsgWithdraw( ctx context.Context, params *types.OutboundParams, height uint64, @@ -53,13 +53,13 @@ func (signer *Signer) SignMsgWithdraw( return msg.SetSignature(signature), nil } -// 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) { +// 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 var inst solana.GenericInstruction inst.DataBytes, err = borsh.Serialize(contracts.WithdrawInstructionParams{ - Discriminator: contracts.DiscriminatorWithdraw(), + Discriminator: contracts.DiscriminatorWithdraw, Amount: msg.Amount(), Signature: msg.SigRS(), RecoveryID: msg.SigV(), diff --git a/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json b/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json index 210d639ead..cf7edb3b81 100644 --- a/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json +++ b/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json @@ -8,9 +8,9 @@ "message": { "accountKeys": [ "AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z", - "2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj", + "9dcAyYG4bawApZocwZSyJBi9Mynf5EuKAJfifXdfkqik", "11111111111111111111111111111111", - "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis" + "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" ], "header": { "numRequiredSignatures": 1, @@ -47,13 +47,13 @@ "preTokenBalances": [], "postTokenBalances": [], "logMessages": [ - "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis invoke [1]", + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d invoke [1]", "Program log: Instruction: Deposit", "Program 11111111111111111111111111111111 invoke [2]", "Program 11111111111111111111111111111111 success", "Program log: AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z deposits 100000 lamports to PDA", - "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis consumed 17006 of 200000 compute units", - "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis success" + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d consumed 17006 of 200000 compute units", + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d success" ], "status": { "Ok": null }, "rewards": [], diff --git a/zetaclient/testdata/solana/chain_901_outbound_tx_result_phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY.json b/zetaclient/testdata/solana/chain_901_outbound_tx_result_phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY.json new file mode 100644 index 0000000000..c488facac3 --- /dev/null +++ b/zetaclient/testdata/solana/chain_901_outbound_tx_result_phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY.json @@ -0,0 +1,76 @@ +{ + "slot": 1109, + "blockTime": 1730732052, + "transaction": { + "signatures": [ + "phM9bESbiqojmpkkUxgjed8EABkxvPGNau9q31B8Yk1sXUtsxJvd6G9VbZZQPsEyn6RiTH4YBtqJ89omqfbbNNY" + ], + "message": { + "accountKeys": [ + "2qBVcNBZCubcnSR3NyCnFjCfkCVUB3G7ECPoaW5rxVjx", + "3eXQYW8nC9142kJUHRgZ9RggJaMgpAEtnZPrwPT7CdxH", + "9dcAyYG4bawApZocwZSyJBi9Mynf5EuKAJfifXdfkqik", + "GNQPa92uBDem5ZFH16TkmFwN5EN8LAzkqeRrxsxZt4eD", + "11111111111111111111111111111111", + "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" + ], + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 3 + }, + "recentBlockhash": "94KjHcf2zDN6VtbFnVU3vkEqJv4d5jWCzBTvg6PtPkdB", + "instructions": [ + { + "programIdIndex": 5, + "accounts": [ + 1, + 3, + 2, + 0, + 4 + ], + "data": "SDhLNtfumZy7dZ96HRWDWWC9NHtnc54NUDt3XAAY8msc42QtH8rF3nYfFcmFjX64KsoMSYNtkWQTv4iVU3Ly36a5ff3nEU5aPbgeBGAPsMbnEiX1bz51dHoyMJjpKxvWJbmCxEG6Z8tA1Tk4EcY39DTDRH" + } + ] + } + }, + "meta": { + "err": null, + "fee": 5000, + "preBalances": [ + 99999985000, + 14947680, + 1461600, + 1, + 1141440 + ], + "postBalances": [ + 99999033440, + 946560, + 14947680, + 1461600, + 1, + 1141440 + ], + "innerInstructions": [], + "preTokenBalances": [], + "postTokenBalances": [], + "logMessages": [ + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d invoke [1]", + "Program log: Instruction: WhitelistSplMint Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program log: recovered address [126, 140, 123, 172, 211, 198, 34, 13, 220, 53, 164, 234, 17, 65, 190, 20, 242, 225, 223, 235]", + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d consumed 46731 of 200000 compute units", + "Program 94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d success" + ], + "status": { "Ok": null }, + "rewards": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "computeUnitsConsumed": 274896977280 + }, + "version": 0 +} \ No newline at end of file diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index e9b8b53563..f776c7019f 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -42,7 +42,9 @@ const ( // GatewayAddresses contains constants gateway addresses for testing var GatewayAddresses = map[int64]string{ // Gateway address on Solana devnet - chains.SolanaDevnet.ChainId: "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis", + // NOTE: currently different deployer key pair is used for development compared to live networks + // as live networks key pair is sensitive information at this point, can be unified once we have deployments completed + chains.SolanaDevnet.ChainId: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d", } // ConnectorAddresses contains constants ERC20 connector addresses for testing From 7474ab5e649a953803fcf60373a0ed99f53bef51 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:07:12 -0600 Subject: [PATCH 25/34] refactor: replace docker-based inscription builder sidecar with Golang implementation (#3082) * initiate bitcoin inscription builder using golang * use the tokenizer in the upgraded btcd package * add changelog entry * fix CI unit test failure * replace 80 with btcd defined constant; update comments * replace hardcoded number with btcd defined constant * fix coderabbit comment and tidy go mod * remove randomness in E2E fee estimation --- changelog.md | 1 + cmd/zetae2e/local/local.go | 1 + contrib/localnet/docker-compose.yml | 12 - e2e/e2etests/e2etests.go | 18 +- ...oin_std_memo_inscribed_deposit_and_call.go | 64 +++++ .../test_extract_bitcoin_inscription_memo.go | 57 ---- e2e/runner/accounting.go | 2 +- e2e/runner/bitcoin.go | 54 ++-- e2e/runner/bitcoin_inscription.go | 259 +++++++++++++----- go.mod | 4 +- go.sum | 2 + zetaclient/chains/bitcoin/observer/witness.go | 3 +- zetaclient/chains/bitcoin/tokenizer.go | 162 ----------- zetaclient/chains/bitcoin/tx_script.go | 6 +- zetaclient/chains/bitcoin/tx_script_test.go | 4 +- 15 files changed, 304 insertions(+), 345 deletions(-) create mode 100644 e2e/e2etests/test_bitcoin_std_memo_inscribed_deposit_and_call.go delete mode 100644 e2e/e2etests/test_extract_bitcoin_inscription_memo.go delete mode 100644 zetaclient/chains/bitcoin/tokenizer.go diff --git a/changelog.md b/changelog.md index e29e6d26af..4ba4b6fcf7 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * [2899](https://github.com/zeta-chain/node/pull/2899) - remove btc deposit fee v1 and improve unit tests * [2952](https://github.com/zeta-chain/node/pull/2952) - add error_message to cctx.status * [3039](https://github.com/zeta-chain/node/pull/3039) - use `btcd` native APIs to handle Bitcoin Taproot address +* [3082](https://github.com/zeta-chain/node/pull/3082) - replace docker-based bitcoin sidecar inscription build with Golang implementation ### Tests diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index e1d579cb0a..986e9e4689 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -310,6 +310,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestBitcoinStdMemoDepositAndCallName, e2etests.TestBitcoinStdMemoDepositAndCallRevertName, e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, + e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, e2etests.TestBitcoinWithdrawSegWitName, e2etests.TestBitcoinWithdrawInvalidAddressName, e2etests.TestZetaWithdrawBTCRevertName, diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index eb6453052f..4f36583d91 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -227,18 +227,6 @@ services: -rpcauth=smoketest:63acf9b8dccecce914d85ff8c044b78b$$5892f9bbc84f4364e79f0970039f88bdd823f168d4acc76099ab97b14a766a99 -txindex=1 - bitcoin-node-sidecar: - image: ghcr.io/zeta-chain/node-localnet-bitcoin-sidecar:e0205d7 - container_name: bitcoin-node-sidecar - hostname: bitcoin-node-sidecar - networks: - mynetwork: - ipv4_address: 172.20.0.111 - environment: - - PORT=8000 - ports: - - "8000:8000" - solana: image: solana-local:latest container_name: solana diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 5f6adf9b87..9f86120bb0 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -81,6 +81,7 @@ const ( TestBitcoinStdMemoDepositAndCallName = "bitcoin_std_memo_deposit_and_call" TestBitcoinStdMemoDepositAndCallRevertName = "bitcoin_std_memo_deposit_and_call_revert" TestBitcoinStdMemoDepositAndCallRevertOtherAddressName = "bitcoin_std_memo_deposit_and_call_revert_other_address" + TestBitcoinStdMemoInscribedDepositAndCallName = "bitcoin_std_memo_inscribed_deposit_and_call" TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" TestBitcoinWithdrawMultipleName = "bitcoin_withdraw_multiple" @@ -89,7 +90,6 @@ const ( TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh" TestBitcoinWithdrawInvalidAddressName = "bitcoin_withdraw_invalid" TestBitcoinWithdrawRestrictedName = "bitcoin_withdraw_restricted" - TestExtractBitcoinInscriptionMemoName = "bitcoin_memo_from_inscription" /* Application tests @@ -497,13 +497,6 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinDonation, ), - runner.NewE2ETest( - TestExtractBitcoinInscriptionMemoName, - "extract memo from BTC inscription", []runner.ArgDefinition{ - {Description: "amount in btc", DefaultValue: "0.1"}, - }, - TestExtractBitcoinInscriptionMemo, - ), runner.NewE2ETest( TestBitcoinDepositName, "deposit Bitcoin into ZEVM", @@ -559,6 +552,15 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinStdMemoDepositAndCallRevertOtherAddress, ), + runner.NewE2ETest( + TestBitcoinStdMemoInscribedDepositAndCallName, + "deposit Bitcoin into ZEVM and call a contract with inscribed standard memo", + []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.1"}, + {Description: "fee rate", DefaultValue: "10"}, + }, + TestBitcoinStdMemoInscribedDepositAndCall, + ), runner.NewE2ETest( TestBitcoinWithdrawSegWitName, "withdraw BTC from ZEVM to a SegWit address", diff --git a/e2e/e2etests/test_bitcoin_std_memo_inscribed_deposit_and_call.go b/e2e/e2etests/test_bitcoin_std_memo_inscribed_deposit_and_call.go new file mode 100644 index 0000000000..c9a5d7af31 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_std_memo_inscribed_deposit_and_call.go @@ -0,0 +1,64 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/memo" + testcontract "github.com/zeta-chain/node/testutil/contracts" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +func TestBitcoinStdMemoInscribedDepositAndCall(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Start mining blocks + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Given amount to send and fee rate + require.Len(r, args, 2) + amount := parseFloat(r, args[0]) + feeRate := parseInt(r, args[1]) + + // deploy an example contract in ZEVM + contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + + // create a standard memo > 80 bytes + memo := &memo.InboundMemo{ + Header: memo.Header{ + Version: 0, + EncodingFmt: memo.EncodingFmtCompactShort, + OpCode: memo.OpCodeDepositAndCall, + }, + FieldsV0: memo.FieldsV0{ + Receiver: contractAddr, + Payload: []byte("for use case that passes a large memo > 80 bytes, inscripting the memo is the way to go"), + }, + } + memoBytes, err := memo.EncodeToBytes() + require.NoError(r, err) + + // ACT + // Send BTC to TSS address with memo + txHash, depositAmount := r.InscribeToTSSFromDeployerWithMemo(amount, memoBytes, int64(feeRate)) + + // ASSERT + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "bitcoin_std_memo_inscribed_deposit_and_call") + utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // check if example contract has been called, 'bar' value should be set to correct amount + depositFeeSats, err := zetabitcoin.GetSatoshis(zetabitcoin.DefaultDepositorFee) + require.NoError(r, err) + receiveAmount := depositAmount - depositFeeSats + utils.MustHaveCalledExampleContract(r, contract, big.NewInt(receiveAmount)) +} diff --git a/e2e/e2etests/test_extract_bitcoin_inscription_memo.go b/e2e/e2etests/test_extract_bitcoin_inscription_memo.go deleted file mode 100644 index eedc24b577..0000000000 --- a/e2e/e2etests/test_extract_bitcoin_inscription_memo.go +++ /dev/null @@ -1,57 +0,0 @@ -package e2etests - -import ( - "encoding/hex" - - "github.com/btcsuite/btcd/btcjson" - "github.com/rs/zerolog/log" - "github.com/stretchr/testify/require" - - "github.com/zeta-chain/node/e2e/runner" - btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" -) - -func TestExtractBitcoinInscriptionMemo(r *runner.E2ERunner, args []string) { - r.SetBtcAddress(r.Name, false) - - // obtain some initial fund - stop := r.MineBlocksIfLocalBitcoin() - defer stop() - r.Logger.Info("Mined blocks") - - // list deployer utxos - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - - amount := parseFloat(r, args[0]) - // this is just some random test memo for inscription - memo, err := hex.DecodeString( - "72f080c854647755d0d9e6f6821f6931f855b9acffd53d87433395672756d58822fd143360762109ab898626556b1c3b8d3096d2361f1297df4a41c1b429471a9aa2fc9be5f27c13b3863d6ac269e4b587d8389f8fd9649859935b0d48dea88cdb40f20c", - ) - require.NoError(r, err) - - txid := r.InscribeToTSSFromDeployerWithMemo(amount, utxos, memo) - - _, err = r.GenerateToAddressIfLocalBitcoin(6, r.BTCDeployerAddress) - require.NoError(r, err) - - rawtx, err := r.BtcRPCClient.GetRawTransactionVerbose(txid) - require.NoError(r, err) - r.Logger.Info("obtained reveal txn id %s", txid) - - dummyCoinbaseTxn := rawtx - events, err := btcobserver.FilterAndParseIncomingTx( - r.BtcRPCClient, - []btcjson.TxRawResult{*dummyCoinbaseTxn, *rawtx}, - 0, - r.BTCTSSAddress.String(), - log.Logger, - r.BitcoinParams, - ) - require.NoError(r, err) - - require.Equal(r, 1, len(events)) - event := events[0] - - require.Equal(r, event.MemoBytes, memo) -} diff --git a/e2e/runner/accounting.go b/e2e/runner/accounting.go index 92e120b7fd..c47883c3f0 100644 --- a/e2e/runner/accounting.go +++ b/e2e/runner/accounting.go @@ -122,7 +122,7 @@ func (r *E2ERunner) CheckBtcTSSBalance() error { ) } // #nosec G115 test - always in range - r.Logger.Print( + r.Logger.Info( "BTC: Balance (%d) >= ZRC20 TotalSupply (%d)", int64(tssTotalBalance*1e8), zrc20Supply.Int64()-10000000, diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 3d65589fa5..047e97139b 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "fmt" - "net/http" "sort" "time" @@ -331,44 +330,47 @@ func (r *E2ERunner) sendToAddrFromDeployerWithMemo( // InscribeToTSSFromDeployerWithMemo creates an inscription that is sent to the tss address with the corresponding memo func (r *E2ERunner) InscribeToTSSFromDeployerWithMemo( amount float64, - inputUTXOs []btcjson.ListUnspentResult, memo []byte, -) *chainhash.Hash { - // TODO: replace builder with Go function to enable instructions - // https://github.com/zeta-chain/node/issues/2759 - builder := InscriptionBuilder{sidecarURL: "http://bitcoin-node-sidecar:8000", client: http.Client{}} - - address, err := builder.GenerateCommitAddress(memo) - require.NoError(r, err) - r.Logger.Info("received inscription commit address %s", address) - - receiver, err := chains.DecodeBtcAddress(address, r.GetBitcoinChainID()) + feeRate int64, +) (*chainhash.Hash, int64) { + // list deployer utxos + utxos, err := r.ListDeployerUTXOs() require.NoError(r, err) - txnHash, err := r.sendToAddrFromDeployerWithMemo(amount, receiver, inputUTXOs, []byte(constant.DonationMessage)) + // generate commit address + builder := NewTapscriptSpender(r.BitcoinParams) + receiver, err := builder.GenerateCommitAddress(memo) require.NoError(r, err) - r.Logger.Info("obtained inscription commit txn hash %s", txnHash.String()) + r.Logger.Info("received inscription commit address: %s", receiver) - // sendToAddrFromDeployerWithMemo makes sure index is 0 - outpointIdx := 0 - hexTx, err := builder.GenerateRevealTxn(r.BTCTSSAddress.String(), txnHash.String(), outpointIdx, amount) + // send funds to the commit address + commitTxHash, err := r.sendToAddrFromDeployerWithMemo(amount, receiver, utxos, nil) require.NoError(r, err) + r.Logger.Info("obtained inscription commit txn hash: %s", commitTxHash.String()) - // Decode the hex string into raw bytes - rawTxBytes, err := hex.DecodeString(hexTx) + // parameters to build the reveal transaction + commitOutputIdx := uint32(0) + commitAmount, err := zetabitcoin.GetSatoshis(amount) require.NoError(r, err) - // Deserialize the raw bytes into a wire.MsgTx structure - msgTx := wire.NewMsgTx(wire.TxVersion) - err = msgTx.Deserialize(bytes.NewReader(rawTxBytes)) + // build the reveal transaction to spend above funds + revealTx, err := builder.BuildRevealTxn( + r.BTCTSSAddress, + wire.OutPoint{ + Hash: *commitTxHash, + Index: commitOutputIdx, + }, + commitAmount, + feeRate, + ) require.NoError(r, err) - r.Logger.Info("recovered inscription reveal txn %s", hexTx) - txid, err := r.BtcRPCClient.SendRawTransaction(msgTx, true) + // submit the reveal transaction + txid, err := r.BtcRPCClient.SendRawTransaction(revealTx, true) require.NoError(r, err) - r.Logger.Info("txid: %+v", txid) + r.Logger.Info("reveal txid: %s", txid.String()) - return txid + return txid, revealTx.TxOut[0].Value } // GetBitcoinChainID gets the bitcoin chain ID from the network params diff --git a/e2e/runner/bitcoin_inscription.go b/e2e/runner/bitcoin_inscription.go index 6f90068905..5ff237391a 100644 --- a/e2e/runner/bitcoin_inscription.go +++ b/e2e/runner/bitcoin_inscription.go @@ -1,119 +1,234 @@ package runner import ( - "bytes" - "encoding/hex" - "encoding/json" "fmt" - "io" - "net/http" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/mempool" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/pkg/errors" ) -type commitResponse struct { - Address string `json:"address"` -} +// TapscriptSpender is a utility struct that helps create Taproot address and reveal transaction +type TapscriptSpender struct { + // internalKey is a local-generated private key used for signing the Taproot script path. + internalKey *btcec.PrivateKey -type revealResponse struct { - RawHex string `json:"rawHex"` -} + // taprootOutputKey is the Taproot output key derived from the internal key and the merkle root. + // It is used to create Taproot addresses that can be funded. + taprootOutputKey *btcec.PublicKey + + // taprootOutputAddr is the Taproot address derived from the taprootOutputKey. + taprootOutputAddr *btcutil.AddressTaproot + + // tapLeaf represents the Taproot leaf node script (tapscript) that contains the embedded inscription data. + tapLeaf txscript.TapLeaf + + // ctrlBlockBytes contains the control block data required for spending the Taproot output via the script path. + // This includes the internal key and proof for the tapLeaf used to authenticate spending. + ctrlBlockBytes []byte -type revealRequest struct { - Txn string `json:"txn"` - Idx int `json:"idx"` - Amount int `json:"amount"` - FeeRate int `json:"feeRate"` - To string `json:"to"` + net *chaincfg.Params } -// InscriptionBuilder is a util struct that help create inscription commit and reveal transactions -type InscriptionBuilder struct { - sidecarURL string - client http.Client +// NewTapscriptSpender creates a new NewTapscriptSpender instance +func NewTapscriptSpender(net *chaincfg.Params) *TapscriptSpender { + return &TapscriptSpender{ + net: net, + } } -// GenerateCommitAddress generates a commit p2tr address that one can send funds to this address -func (r *InscriptionBuilder) GenerateCommitAddress(memo []byte) (string, error) { - // Create the payload - postData := map[string]string{ - "memo": hex.EncodeToString(memo), +// GenerateCommitAddress generates a Taproot commit address for the given receiver and payload +func (s *TapscriptSpender) GenerateCommitAddress(memo []byte) (*btcutil.AddressTaproot, error) { + // OP_RETURN is a better choice for memo <= 80 bytes + if len(memo) <= txscript.MaxDataCarrierSize { + return nil, fmt.Errorf("OP_RETURN is a better choice for memo <= 80 bytes") } - // Convert the payload to JSON - jsonData, err := json.Marshal(postData) + // generate internal private key, leaf script and Taproot output key + err := s.genTaprootLeafAndKeys(memo) if err != nil { - return "", err + return nil, errors.Wrap(err, "genTaprootLeafAndKeys failed") } - postURL := r.sidecarURL + "/commit" - req, err := http.NewRequest("POST", postURL, bytes.NewBuffer(jsonData)) + return s.taprootOutputAddr, nil +} + +// BuildRevealTxn returns a signed reveal transaction that spends the commit transaction +func (s *TapscriptSpender) BuildRevealTxn( + to btcutil.Address, + commitTxn wire.OutPoint, + commitAmount int64, + feeRate int64, +) (*wire.MsgTx, error) { + // Step 1: create tx message + revealTx := wire.NewMsgTx(2) + + // Step 2: add input (the commit tx) + outpoint := wire.NewOutPoint(&commitTxn.Hash, commitTxn.Index) + revealTx.AddTxIn(wire.NewTxIn(outpoint, nil, nil)) + + // Step 3: add output (to TSS) + pkScript, err := txscript.PayToAddrScript(to) if err != nil { - return "", errors.Wrap(err, "cannot create commit request") + return nil, errors.Wrap(err, "failed to create receiver pkScript") } - req.Header.Set("Content-Type", "application/json") - - // Send the request - resp, err := r.client.Do(req) + fee, err := s.estimateFee(revealTx, to, commitAmount, feeRate) if err != nil { - return "", errors.Wrap(err, "cannot send to sidecar") + return nil, errors.Wrap(err, "failed to estimate fee for reveal txn") } - defer resp.Body.Close() + revealTx.AddTxOut(wire.NewTxOut(commitAmount-fee, pkScript)) - // Read the response body - var response commitResponse - err = json.NewDecoder(resp.Body).Decode(&response) + // Step 4: compute the sighash for the P2TR input to be spent using script path + commitScript, err := txscript.PayToAddrScript(s.taprootOutputAddr) if err != nil { - return "", err + return nil, errors.Wrap(err, "failed to create commit pkScript") + } + prevOutFetcher := txscript.NewCannedPrevOutputFetcher(commitScript, commitAmount) + sigHashes := txscript.NewTxSigHashes(revealTx, prevOutFetcher) + sigHash, err := txscript.CalcTapscriptSignaturehash( + sigHashes, + txscript.SigHashDefault, + revealTx, + int(commitTxn.Index), + prevOutFetcher, + s.tapLeaf, + ) + if err != nil { + return nil, errors.Wrap(err, "failed to calculate tapscript sighash") } - fmt.Print("raw commit response ", response.Address) + // Step 5: sign the sighash with the internal key + sig, err := schnorr.Sign(s.internalKey, sigHash) + if err != nil { + return nil, errors.Wrap(err, "failed to sign sighash") + } + revealTx.TxIn[0].Witness = wire.TxWitness{sig.Serialize(), s.tapLeaf.Script, s.ctrlBlockBytes} - return response.Address, nil + return revealTx, nil } -// GenerateRevealTxn creates the corresponding reveal txn to the commit txn. -func (r *InscriptionBuilder) GenerateRevealTxn(to string, txnHash string, idx int, amount float64) (string, error) { - postData := revealRequest{ - Txn: txnHash, - Idx: idx, - Amount: int(amount * 100000000), - FeeRate: 10, - To: to, +// genTaprootLeafAndKeys generates internal private key, leaf script and Taproot output key +func (s *TapscriptSpender) genTaprootLeafAndKeys(data []byte) error { + // generate an internal private key + internalKey, err := btcec.NewPrivateKey() + if err != nil { + return errors.Wrap(err, "failed to generate internal private key") } - // Convert the payload to JSON - jsonData, err := json.Marshal(postData) + // generate the leaf script + leafScript, err := genLeafScript(internalKey.PubKey(), data) if err != nil { - return "", err + return errors.Wrap(err, "failed to generate leaf script") } - postURL := r.sidecarURL + "/reveal" - req, err := http.NewRequest("POST", postURL, bytes.NewBuffer(jsonData)) + // assemble Taproot tree + tapLeaf := txscript.NewBaseTapLeaf(leafScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + + // compute the Taproot output key and address + tapScriptRoot := tapScriptTree.RootNode.TapHash() + taprootOutputKey := txscript.ComputeTaprootOutputKey(internalKey.PubKey(), tapScriptRoot[:]) + taprootOutputAddr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(taprootOutputKey), s.net) if err != nil { - return "", errors.Wrap(err, "cannot create reveal request") + return errors.Wrap(err, "failed to create Taproot address") } - req.Header.Set("Content-Type", "application/json") - // Send the request - resp, err := r.client.Do(req) + // construct the control block for the Taproot leaf script. + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(internalKey.PubKey()) + ctrlBlockBytes, err := ctrlBlock.ToBytes() if err != nil { - return "", errors.Wrap(err, "cannot send reveal to sidecar") + return errors.Wrap(err, "failed to serialize control block") } - defer resp.Body.Close() - // Read the response body - body, err := io.ReadAll(resp.Body) + // save generated keys, script and control block for later use + s.internalKey = internalKey + s.taprootOutputKey = taprootOutputKey + s.taprootOutputAddr = taprootOutputAddr + s.tapLeaf = tapLeaf + s.ctrlBlockBytes = ctrlBlockBytes + + return nil +} + +// estimateFee estimates the tx fee based given fee rate and estimated tx virtual size +func (s *TapscriptSpender) estimateFee( + tx *wire.MsgTx, + to btcutil.Address, + amount int64, + feeRate int64, +) (int64, error) { + txCopy := tx.Copy() + + // add output to the copied transaction + pkScript, err := txscript.PayToAddrScript(to) if err != nil { - return "", errors.Wrap(err, "cannot read reveal response body") + return 0, err } + txCopy.AddTxOut(wire.NewTxOut(amount, pkScript)) + + // create 64-byte fake Schnorr signature + sigBytes := make([]byte, 64) + + // set the witness for the first input + txWitness := wire.TxWitness{sigBytes, s.tapLeaf.Script, s.ctrlBlockBytes} + txCopy.TxIn[0].Witness = txWitness + + // calculate the fee based on the estimated virtual size + fee := mempool.GetTxVirtualSize(btcutil.NewTx(txCopy)) * feeRate + + return fee, nil +} + +//================================================================================================= +//================================================================================================= + +// LeafScriptBuilder represents a builder for Taproot leaf scripts +type LeafScriptBuilder struct { + script txscript.ScriptBuilder +} + +// NewLeafScriptBuilder initializes a new LeafScriptBuilder with a public key and `OP_CHECKSIG` +func NewLeafScriptBuilder(pubKey *btcec.PublicKey) *LeafScriptBuilder { + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(pubKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + return &LeafScriptBuilder{script: *builder} +} - // Parse the JSON response - var response revealResponse - if err := json.Unmarshal(body, &response); err != nil { - return "", errors.Wrap(err, "cannot parse reveal response body") +// PushData adds a large data to the Taproot leaf script following OP_FALSE and OP_IF structure +func (b *LeafScriptBuilder) PushData(data []byte) { + // start the inscription envelope + b.script.AddOp(txscript.OP_FALSE) + b.script.AddOp(txscript.OP_IF) + + // break data into chunks and push each one + dataLen := len(data) + for i := 0; i < dataLen; i += txscript.MaxScriptElementSize { + if dataLen-i >= txscript.MaxScriptElementSize { + b.script.AddData(data[i : i+txscript.MaxScriptElementSize]) + } else { + b.script.AddData(data[i:]) + } } - // Access the "address" field - return response.RawHex, nil + // end the inscription envelope + b.script.AddOp(txscript.OP_ENDIF) +} + +// Script returns the current script +func (b *LeafScriptBuilder) Script() ([]byte, error) { + return b.script.Script() +} + +// genLeafScript creates a Taproot leaf script using provided pubkey and data +func genLeafScript(pubKey *btcec.PublicKey, data []byte) ([]byte, error) { + builder := NewLeafScriptBuilder(pubKey) + builder.PushData(data) + return builder.Script() } diff --git a/go.mod b/go.mod index 13a35b26bc..c92e2d2767 100644 --- a/go.mod +++ b/go.mod @@ -334,14 +334,16 @@ require ( require ( github.com/bnb-chain/tss-lib v1.5.0 + github.com/montanaflynn/stats v0.7.1 github.com/showa-93/go-mask v0.6.2 github.com/tonkeeper/tongo v1.9.3 github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b ) require ( + github.com/aead/siphash v1.0.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/montanaflynn/stats v0.7.1 // indirect + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index fec35684fa..404160ddd4 100644 --- a/go.sum +++ b/go.sum @@ -1417,6 +1417,7 @@ github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -2987,6 +2988,7 @@ github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.3/go.mod h1:PG/cwd6c0705/LM0KTr1acO2gORUxkSVWyLJOFW5qoo= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= diff --git a/zetaclient/chains/bitcoin/observer/witness.go b/zetaclient/chains/bitcoin/observer/witness.go index 22ce75719b..9625ad3caa 100644 --- a/zetaclient/chains/bitcoin/observer/witness.go +++ b/zetaclient/chains/bitcoin/observer/witness.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -104,7 +105,7 @@ func ParseScriptFromWitness(witness []string, logger zerolog.Logger) []byte { // If there are at least two witness elements, and the first byte of // the last element is 0x50, this last element is called annex a // and is removed from the witness stack. - if length >= 2 && len(lastElement) > 0 && lastElement[0] == 0x50 { + if length >= 2 && len(lastElement) > 0 && lastElement[0] == txscript.TaprootAnnexTag { // account for the extra item removed from the end witness = witness[:length-1] } diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go deleted file mode 100644 index 5708bfa250..0000000000 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ /dev/null @@ -1,162 +0,0 @@ -package bitcoin - -import ( - "encoding/binary" - "fmt" - - "github.com/btcsuite/btcd/txscript" -) - -func newScriptTokenizer(script []byte) scriptTokenizer { - return scriptTokenizer{ - script: script, - offset: 0, - } -} - -// scriptTokenizer is supposed to be replaced by txscript.ScriptTokenizer. However, -// it seems currently the btcsuite version does not have ScriptTokenizer. A simplified -// version of that is implemented here. This is fully compatible with txscript.ScriptTokenizer -// one should consider upgrading txscript and remove this implementation -type scriptTokenizer struct { - script []byte - offset int - op byte - data []byte - err error -} - -// Done returns true when either all opcodes have been exhausted or a parse -// failure was encountered and therefore the state has an associated error. -func (t *scriptTokenizer) Done() bool { - return t.err != nil || t.offset >= len(t.script) -} - -// Data returns the data associated with the most recently successfully parsed -// opcode. -func (t *scriptTokenizer) Data() []byte { - return t.data -} - -// Err returns any errors currently associated with the tokenizer. This will -// only be non-nil in the case a parsing error was encountered. -func (t *scriptTokenizer) Err() error { - return t.err -} - -// Opcode returns the current opcode associated with the tokenizer. -func (t *scriptTokenizer) Opcode() byte { - return t.op -} - -// Next attempts to parse the next opcode and returns whether or not it was -// successful. It will not be successful if invoked when already at the end of -// the script, a parse failure is encountered, or an associated error already -// exists due to a previous parse failure. -// -// In the case of a true return, the parsed opcode and data can be obtained with -// the associated functions and the offset into the script will either point to -// the next opcode or the end of the script if the final opcode was parsed. -// -// In the case of a false return, the parsed opcode and data will be the last -// successfully parsed values (if any) and the offset into the script will -// either point to the failing opcode or the end of the script if the function -// was invoked when already at the end of the script. -// -// Invoking this function when already at the end of the script is not -// considered an error and will simply return false. -func (t *scriptTokenizer) Next() bool { - if t.Done() { - return false - } - - op := t.script[t.offset] - - // Only the following op_code will be encountered: - // OP_PUSHDATA*, OP_DATA_*, OP_CHECKSIG, OP_IF, OP_ENDIF, OP_FALSE - switch { - // No additional data. Note that some of the opcodes, notably OP_1NEGATE, - // OP_0, and OP_[1-16] represent the data themselves. - case op == txscript.OP_FALSE || op == txscript.OP_IF || op == txscript.OP_CHECKSIG || op == txscript.OP_ENDIF: - t.offset++ - t.op = op - t.data = nil - return true - - // Data pushes of specific lengths -- OP_DATA_[1-75]. - case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: - script := t.script[t.offset:] - - // The length should be: int(op) - txscript.OP_DATA_1 + 2, i.e. op is txscript.OP_DATA_10, that means - // the data length should be 10, which is txscript.OP_DATA_10 - txscript.OP_DATA_1 + 1. - // Here, 2 instead of 1 because `script` also includes the opcode which means it contains one more byte. - // Since txscript.OP_DATA_1 is 1, then length is just int(op) - 1 + 2 = int(op) + 1 - length := int(op) + 1 - if len(script) < length { - t.err = fmt.Errorf("opcode %d detected, but script only %d bytes remaining", op, len(script)) - return false - } - - // Move the offset forward and set the opcode and data accordingly. - t.offset += length - t.op = op - t.data = script[1:length] - return true - - case op > txscript.OP_PUSHDATA4: - t.err = fmt.Errorf("unexpected op code %d", op) - return false - - // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. - default: - var length int - switch op { - case txscript.OP_PUSHDATA1: - length = 1 - case txscript.OP_PUSHDATA2: - length = 2 - case txscript.OP_PUSHDATA4: - length = 4 - default: - t.err = fmt.Errorf("unexpected op code %d", op) - return false - } - - script := t.script[t.offset+1:] - if len(script) < length { - t.err = fmt.Errorf("opcode %d requires %d bytes, only %d remaining", op, length, len(script)) - return false - } - - // Next -length bytes are little endian length of data. - var dataLen int - switch length { - case 1: - dataLen = int(script[0]) - case 2: - dataLen = int(binary.LittleEndian.Uint16(script[:length])) - case 4: - dataLen = int(binary.LittleEndian.Uint32(script[:length])) - default: - t.err = fmt.Errorf("invalid opcode length %d", length) - return false - } - - // Move to the beginning of the data. - script = script[length:] - - // Disallow entries that do not fit script or were sign extended. - if dataLen > len(script) || dataLen < 0 { - t.err = fmt.Errorf("opcode %d pushes %d bytes, only %d remaining", op, dataLen, len(script)) - return false - } - - // Move the offset forward and set the opcode and data accordingly. - // 1 is the opcode size, which is just 1 byte. int(op) is the opcode value, - // it should not be mixed with the size. - t.offset += 1 + length + dataLen - t.op = op - t.data = script[:dataLen] - return true - } -} diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 6f394ef81d..816251024a 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -216,7 +216,7 @@ func DecodeOpReturnMemo(scriptHex string) ([]byte, bool, error) { // OP_ENDIF // There are no content-type or any other attributes, it's just raw bytes. func DecodeScript(script []byte) ([]byte, bool, error) { - t := newScriptTokenizer(script) + t := txscript.MakeScriptTokenizer(0, script) if err := checkInscriptionEnvelope(&t); err != nil { return nil, false, errors.Wrap(err, "checkInscriptionEnvelope: unable to check the envelope") @@ -306,7 +306,7 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai return receiverVout, amount, nil } -func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { +func decodeInscriptionPayload(t *txscript.ScriptTokenizer) ([]byte, error) { if !t.Next() || t.Opcode() != txscript.OP_FALSE { return nil, fmt.Errorf("OP_FALSE not found") } @@ -335,7 +335,7 @@ func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { // checkInscriptionEnvelope decodes the envelope for the script monitoring. The format is // OP_PUSHBYTES_32 <32 bytes> OP_CHECKSIG -func checkInscriptionEnvelope(t *scriptTokenizer) error { +func checkInscriptionEnvelope(t *txscript.ScriptTokenizer) error { if !t.Next() || t.Opcode() != txscript.OP_DATA_32 { return fmt.Errorf("cannot obtain public key bytes op %d or err %s", t.Opcode(), t.Err()) } diff --git a/zetaclient/chains/bitcoin/tx_script_test.go b/zetaclient/chains/bitcoin/tx_script_test.go index 394a5d8608..6c4724eb9a 100644 --- a/zetaclient/chains/bitcoin/tx_script_test.go +++ b/zetaclient/chains/bitcoin/tx_script_test.go @@ -659,8 +659,8 @@ func TestDecodeScript(t *testing.T) { }) t.Run("decode error due to missing data for public key", func(t *testing.T) { - // missing OP_ENDIF at the end - data := "2001a7bae79bd61c2368fe41a565061d6cf22b4f509fbc1652caea06d98b8fd0" + // require OP_DATA_32 but OP_DATA_31 is given + data := "1f01a7bae79bd61c2368fe41a565061d6cf22b4f509fbc1652caea06d98b8fd0" script, _ := hex.DecodeString(data) memo, isFound, err := bitcoin.DecodeScript(script) From 08eeb7f9c130255bc7199a42aed14ce4190b9c6c Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Mon, 4 Nov 2024 09:07:36 -0800 Subject: [PATCH 26/34] fix(ci): do not run rpcimportable on forks (#3087) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 741931df27..52c31eeaa9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,9 @@ jobs: rpcimportable: runs-on: ubuntu-20.04 timeout-minutes: 15 + # do not run this on forks as they are not installable + # it will still be check in the merge queue in this case + if: github.repository == 'zeta-chain/node' steps: - uses: actions/checkout@v4 - name: Set up Go From 44c377a3bdea16c3e4e4ef83c182b4522e323ba5 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Mon, 4 Nov 2024 11:09:58 -0800 Subject: [PATCH 27/34] fix(ci): do not run rpcimportable on forks (#3089) --- .github/workflows/ci.yml | 2 +- .github/workflows/reusable-e2e.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52c31eeaa9..8a44e995a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: timeout-minutes: 15 # do not run this on forks as they are not installable # it will still be check in the merge queue in this case - if: github.repository == 'zeta-chain/node' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'zeta-chain/node' steps: - uses: actions/checkout@v4 - name: Set up Go diff --git a/.github/workflows/reusable-e2e.yml b/.github/workflows/reusable-e2e.yml index aaac140960..88c42466a5 100644 --- a/.github/workflows/reusable-e2e.yml +++ b/.github/workflows/reusable-e2e.yml @@ -40,7 +40,7 @@ jobs: - name: Login to Docker Hub registry uses: docker/login-action@v3 - if: (github.event_name == 'push' && github.repository == 'zeta-chain/node') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'zeta-chain/node') + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'zeta-chain/node' with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_READ_ONLY }} From 041df59fe4d012ecd293a0bb530e1c25dae9a381 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:39:04 +0100 Subject: [PATCH 28/34] feat(ton): adjacent TON tasks (#3075) * Implement InboundTracker; minor refactoring * E2E: Concurrent withdrawals [WIP] * E2E: Concurrent withdrawals * Improve signer broadcasting & logging * Add e2e for deposit & refund * Update changelog * Address PR comments * Address PR comments [2] --- changelog.md | 3 + cmd/zetae2e/local/local.go | 2 + e2e/e2etests/e2etests.go | 22 +++- e2e/e2etests/test_ton_deposit_refund.go | 50 ++++++++ e2e/e2etests/test_ton_withdrawal.go | 7 +- .../test_ton_withdrawal_concurrent.go | 74 +++++++++++ e2e/runner/setup_ton.go | 35 +++-- e2e/runner/ton.go | 47 ++++++- e2e/runner/ton/deployer.go | 11 +- e2e/runner/zeta.go | 9 +- pkg/contracts/ton/gateway_msg.go | 9 ++ zetaclient/chains/ton/observer/inbound.go | 103 ++++++++++++--- .../chains/ton/observer/inbound_test.go | 120 +++++++++++++----- zetaclient/chains/ton/observer/observer.go | 19 ++- .../chains/ton/observer/observer_test.go | 21 +++ zetaclient/chains/ton/signer/signer.go | 93 +++++++++++--- zetaclient/chains/ton/signer/signer_test.go | 25 ++++ zetaclient/orchestrator/orchestrator.go | 7 +- 18 files changed, 546 insertions(+), 111 deletions(-) create mode 100644 e2e/e2etests/test_ton_deposit_refund.go create mode 100644 e2e/e2etests/test_ton_withdrawal_concurrent.go diff --git a/changelog.md b/changelog.md index 4ba4b6fcf7..3c2cd59ccb 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,9 @@ * [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana +### Tests +* [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert. + ## v21.0.0 ### Features diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 986e9e4689..d5b4ac8076 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -426,7 +426,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) { tonTests := []string{ e2etests.TestTONDepositName, e2etests.TestTONDepositAndCallName, + e2etests.TestTONDepositAndCallRefundName, e2etests.TestTONWithdrawName, + e2etests.TestTONWithdrawConcurrentName, } eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...)) diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 9f86120bb0..82764cb537 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -65,9 +65,11 @@ const ( /** * TON tests */ - TestTONDepositName = "ton_deposit" - TestTONDepositAndCallName = "ton_deposit_and_call" - TestTONWithdrawName = "ton_withdraw" + TestTONDepositName = "ton_deposit" + TestTONDepositAndCallName = "ton_deposit_and_call" + TestTONDepositAndCallRefundName = "ton_deposit_refund" + TestTONWithdrawName = "ton_withdraw" + TestTONWithdrawConcurrentName = "ton_withdraw_concurrent" /* Bitcoin tests @@ -479,6 +481,14 @@ var AllE2ETests = []runner.E2ETest{ }, TestTONDepositAndCall, ), + runner.NewE2ETest( + TestTONDepositAndCallRefundName, + "deposit TON into ZEVM and call a smart contract that reverts; expect refund", + []runner.ArgDefinition{ + {Description: "amount in nano tons", DefaultValue: "1000000000"}, // 1.0 TON + }, + TestTONDepositAndCallRefund, + ), runner.NewE2ETest( TestTONWithdrawName, "withdraw TON from ZEVM", @@ -487,6 +497,12 @@ var AllE2ETests = []runner.E2ETest{ }, TestTONWithdraw, ), + runner.NewE2ETest( + TestTONWithdrawConcurrentName, + "withdraw TON from ZEVM for several recipients simultaneously", + []runner.ArgDefinition{}, + TestTONWithdrawConcurrent, + ), /* Bitcoin tests */ diff --git a/e2e/e2etests/test_ton_deposit_refund.go b/e2e/e2etests/test_ton_deposit_refund.go new file mode 100644 index 0000000000..3259e1d1d6 --- /dev/null +++ b/e2e/e2etests/test_ton_deposit_refund.go @@ -0,0 +1,50 @@ +package e2etests + +import ( + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + testcontract "github.com/zeta-chain/node/testutil/contracts" + cctypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestTONDepositAndCallRefund(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + // Given amount and arbitrary call data + var ( + amount = parseUint(r, args[0]) + data = []byte("hello reverter") + ) + + // Given deployer mock revert contract + // deploy a reverter contract in ZEVM + reverterAddr, _, _, err := testcontract.DeployReverter(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + r.Logger.Info("Reverter contract deployed at: %s", reverterAddr.String()) + + // ACT + // Send a deposit and call transaction from the deployer (faucet) + // to the reverter contract + cctx, err := r.TONDepositAndCall( + &r.TONDeployer.Wallet, + amount, + reverterAddr, + data, + runner.TONExpectStatus(cctypes.CctxStatus_Reverted), + ) + + // ASSERT + require.NoError(r, err) + r.Logger.CCTX(*cctx, "ton_deposit_and_refund") + + // Check the error carries the revert executed. + // tolerate the error in both the ErrorMessage field and the StatusMessage field + if cctx.CctxStatus.ErrorMessage != "" { + require.Contains(r, cctx.CctxStatus.ErrorMessage, "revert executed") + return + } + + require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo) +} diff --git a/e2e/e2etests/test_ton_withdrawal.go b/e2e/e2etests/test_ton_withdrawal.go index d78f624716..db75fa4d28 100644 --- a/e2e/e2etests/test_ton_withdrawal.go +++ b/e2e/e2etests/test_ton_withdrawal.go @@ -12,9 +12,6 @@ import ( "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" ) -// TODO: Add "withdraw_many_concurrent" test -// https://github.com/zeta-chain/node/issues/3044 - func TestTONWithdraw(r *runner.E2ERunner, args []string) { // ARRANGE require.Len(r, args, 1) @@ -34,7 +31,7 @@ func TestTONWithdraw(r *runner.E2ERunner, args []string) { tonRecipient, err := deployer.CreateWallet(r.Ctx, toncontracts.Coins(1)) require.NoError(r, err) - tonRecipientBalanceBefore, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress()) + tonRecipientBalanceBefore, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress(), true) require.NoError(r, err) r.Logger.Info("Recipient's TON balance before withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceBefore)) @@ -61,7 +58,7 @@ func TestTONWithdraw(r *runner.E2ERunner, args []string) { ) // Make sure that recipient's TON balance has increased - tonRecipientBalanceAfter, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress()) + tonRecipientBalanceAfter, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress(), true) require.NoError(r, err) r.Logger.Info("Recipient's balance after withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceAfter)) diff --git a/e2e/e2etests/test_ton_withdrawal_concurrent.go b/e2e/e2etests/test_ton_withdrawal_concurrent.go new file mode 100644 index 0000000000..fb4217013d --- /dev/null +++ b/e2e/e2etests/test_ton_withdrawal_concurrent.go @@ -0,0 +1,74 @@ +package e2etests + +import ( + "math/rand" + "sync" + + "cosmossdk.io/math" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/ton" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + "github.com/zeta-chain/node/testutil/sample" + cc "github.com/zeta-chain/node/x/crosschain/types" +) + +// TestTONWithdrawConcurrent makes sure that multiple concurrent +// withdrawals will be eventually processed by sequentially increasing Gateway nonce +// and that zetaclient tolerates "invalid nonce" error from RPC. +func TestTONWithdrawConcurrent(r *runner.E2ERunner, _ []string) { + // ARRANGE + // Given a deployer + _, deployer := r.Ctx, r.TONDeployer + + const recipientsCount = 10 + + // Fire withdrawals. Note that zevm sender is r.ZEVMAuth + var wg sync.WaitGroup + for i := 0; i < recipientsCount; i++ { + // ARRANGE + // Given multiple recipients WITHOUT deployed wallet-contracts + // and withdrawal amounts between 1 and 5 TON + var ( + // #nosec G404: it's a test + amountCoins = 1 + rand.Intn(5) + // #nosec G115 test - always in range + amount = toncontracts.Coins(uint64(amountCoins)) + recipient = sample.GenerateTONAccountID() + ) + + // ACT + r.Logger.Info( + "Withdrawal #%d: sending %s to %s", + i+1, + toncontracts.FormatCoins(amount), + recipient.ToRaw(), + ) + + approvedAmount := amount.Add(toncontracts.Coins(1)) + tx := r.SendWithdrawTONZRC20(recipient, amount.BigInt(), approvedAmount.BigInt()) + + wg.Add(1) + + go func(number int, recipient ton.AccountID, amount math.Uint, tx *ethtypes.Transaction) { + defer wg.Done() + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + // ASSERT + utils.RequireCCTXStatus(r, cctx, cc.CctxStatus_OutboundMined) + r.Logger.Info("Withdrawal #%d complete! cctx index: %s", number, cctx.Index) + + // Check recipient's balance ON TON + balance, err := deployer.GetBalanceOf(r.Ctx, recipient, false) + require.NoError(r, err, "failed to get balance of %s", recipient.ToRaw()) + require.Equal(r, amount.Uint64(), balance.Uint64()) + }(i+1, recipient, amount, tx) + } + + wg.Wait() +} diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go index 49feb5d95a..10954b5f43 100644 --- a/e2e/runner/setup_ton.go +++ b/e2e/runner/setup_ton.go @@ -11,6 +11,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/constant" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" + cctxtypes "github.com/zeta-chain/node/x/crosschain/types" observertypes "github.com/zeta-chain/node/x/observer/types" ) @@ -54,29 +55,35 @@ func (r *E2ERunner) SetupTON() error { ) // 3. Check that the gateway indeed was deployed and has desired TON balance. - gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID) - if err != nil { + gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID, true) + switch { + case err != nil: return errors.Wrap(err, "unable to get balance of TON gateway") + case gwBalance.IsZero(): + return fmt.Errorf("TON gateway balance is zero") } - if gwBalance.IsZero() { - return fmt.Errorf("TON gateway balance is zero") + // 4. Set chain params & chain nonce + if err := r.ensureTONChainParams(gwAccount); err != nil { + return errors.Wrap(err, "unable to ensure TON chain params") } - // 4. Deposit 100 TON deployer to Zevm Auth - gw := toncontracts.NewGateway(gwAccount.ID) - veryFirstDeposit := toncontracts.Coins(1000) + r.TONDeployer = deployer + r.TONGateway = toncontracts.NewGateway(gwAccount.ID) + + // 5. Deposit 10000 TON deployer to Zevm Auth + veryFirstDeposit := toncontracts.Coins(10000) zevmRecipient := r.ZEVMAuth.From - err = gw.SendDeposit(ctx, &deployer.Wallet, veryFirstDeposit, zevmRecipient, 0) - if err != nil { - return errors.Wrap(err, "unable to send deposit to TON gateway") + gwDeposit, err := r.TONDeposit(&deployer.Wallet, veryFirstDeposit, zevmRecipient) + switch { + case err != nil: + return errors.Wrap(err, "unable to deposit TON to Zevm Auth") + case gwDeposit.CctxStatus.Status != cctxtypes.CctxStatus_OutboundMined: + return errors.New("gateway deposit CCTX is not mined") } - r.TONDeployer = deployer - r.TONGateway = gw - - return r.ensureTONChainParams(gwAccount) + return nil } func (r *E2ERunner) ensureTONChainParams(gw *ton.AccountInit) error { diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go index eb8e5346ac..369bfa0df3 100644 --- a/e2e/runner/ton.go +++ b/e2e/runner/ton.go @@ -1,11 +1,13 @@ package runner import ( + "encoding/hex" "math/big" "time" "cosmossdk.io/math" eth "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/tonkeeper/tongo/ton" @@ -23,6 +25,20 @@ import ( // https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors +// currently implemented only for DepositAndCall, +// can be adopted for all TON ops +type tonOpts struct { + expectedStatus cctypes.CctxStatus +} + +type TONOpt func(t *tonOpts) + +func TONExpectStatus(status cctypes.CctxStatus) TONOpt { + return func(t *tonOpts) { + t.expectedStatus = status + } +} + // TONDeposit deposit TON to Gateway contract func (r *E2ERunner) TONDeposit( sender *wallet.Wallet, @@ -56,7 +72,7 @@ func (r *E2ERunner) TONDeposit( } // Wait for cctx - cctx := r.WaitForSpecificCCTX(filter, time.Minute) + cctx := r.WaitForSpecificCCTX(filter, cctypes.CctxStatus_OutboundMined, time.Minute) return cctx, nil } @@ -67,7 +83,13 @@ func (r *E2ERunner) TONDepositAndCall( amount math.Uint, zevmRecipient eth.Address, callData []byte, + opts ...TONOpt, ) (*cctypes.CrossChainTx, error) { + cfg := &tonOpts{expectedStatus: cctypes.CctxStatus_OutboundMined} + for _, opt := range opts { + opt(cfg) + } + chain := chains.TONLocalnet require.NotNil(r, r.TONGateway, "TON Gateway is not initialized") @@ -91,18 +113,26 @@ func (r *E2ERunner) TONDepositAndCall( } filter := func(cctx *cctypes.CrossChainTx) bool { + memo := zevmRecipient.Bytes() + memo = append(memo, callData...) + return cctx.InboundParams.SenderChainId == chain.ChainId && - cctx.InboundParams.Sender == sender.GetAddress().ToRaw() + cctx.InboundParams.Sender == sender.GetAddress().ToRaw() && + cctx.RelayedMessage == hex.EncodeToString(memo) } // Wait for cctx - cctx := r.WaitForSpecificCCTX(filter, time.Minute) + cctx := r.WaitForSpecificCCTX(filter, cfg.expectedStatus, time.Minute) return cctx, nil } -// WithdrawTONZRC20 withdraws an amount of ZRC20 TON tokens -func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveAmount *big.Int) *cctypes.CrossChainTx { +// SendWithdrawTONZRC20 sends withdraw tx of TON ZRC20 tokens +func (r *E2ERunner) SendWithdrawTONZRC20( + to ton.AccountID, + amount *big.Int, + approveAmount *big.Int, +) *ethtypes.Transaction { // approve tx, err := r.TONZRC20.Approve(r.ZEVMAuth, r.TONZRC20Addr, approveAmount) require.NoError(r, err) @@ -119,6 +149,13 @@ func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveA utils.RequireTxSuccessful(r, receipt, "withdraw") r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + return tx +} + +// WithdrawTONZRC20 withdraws an amount of ZRC20 TON tokens and waits for the cctx to be mined +func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveAmount *big.Int) *cctypes.CrossChainTx { + tx := r.SendWithdrawTONZRC20(to, amount, approveAmount) + // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) utils.RequireCCTXStatus(r, cctx, cctypes.CctxStatus_OutboundMined) diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go index 4735d3be10..3f12f784da 100644 --- a/e2e/runner/ton/deployer.go +++ b/e2e/runner/ton/deployer.go @@ -57,10 +57,13 @@ func (d *Deployer) Seqno(ctx context.Context) (uint32, error) { return d.blockchain.GetSeqno(ctx, d.GetAddress()) } -// GetBalanceOf returns the balance of the given account. -func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID) (math.Uint, error) { - if err := d.waitForAccountActivation(ctx, id); err != nil { - return math.Uint{}, errors.Wrap(err, "failed to wait for account activation") +// GetBalanceOf returns the balance of a given account. +// wait=true waits for account activation. +func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID, wait bool) (math.Uint, error) { + if wait { + if err := d.waitForAccountActivation(ctx, id); err != nil { + return math.Uint{}, errors.Wrap(err, "failed to wait for account activation") + } } state, err := d.blockchain.GetAccountState(ctx, id) diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go index 1df7e676e3..d59ef85645 100644 --- a/e2e/runner/zeta.go +++ b/e2e/runner/zeta.go @@ -99,11 +99,15 @@ func (r *E2ERunner) WaitForMinedCCTX(txHash ethcommon.Hash) { // WaitForMinedCCTXFromIndex waits for a cctx to be mined from its index func (r *E2ERunner) WaitForMinedCCTXFromIndex(index string) *types.CrossChainTx { + return r.waitForMinedCCTXFromIndex(index, types.CctxStatus_OutboundMined) +} + +func (r *E2ERunner) waitForMinedCCTXFromIndex(index string, status types.CctxStatus) *types.CrossChainTx { r.Lock() defer r.Unlock() cctx := utils.WaitCCTXMinedByIndex(r.Ctx, index, r.CctxClient, r.Logger, r.CctxTimeout) - utils.RequireCCTXStatus(r, cctx, types.CctxStatus_OutboundMined) + utils.RequireCCTXStatus(r, cctx, status) return cctx } @@ -111,6 +115,7 @@ func (r *E2ERunner) WaitForMinedCCTXFromIndex(index string) *types.CrossChainTx // WaitForSpecificCCTX scans for cctx by filters and ensures it's mined func (r *E2ERunner) WaitForSpecificCCTX( filter func(*types.CrossChainTx) bool, + status types.CctxStatus, timeout time.Duration, ) *types.CrossChainTx { var ( @@ -128,7 +133,7 @@ func (r *E2ERunner) WaitForSpecificCCTX( for i := range res.CrossChainTx { tx := res.CrossChainTx[i] if filter(tx) { - return r.WaitForMinedCCTXFromIndex(tx.Index) + return r.waitForMinedCCTXFromIndex(tx.Index, status) } } diff --git a/pkg/contracts/ton/gateway_msg.go b/pkg/contracts/ton/gateway_msg.go index 543f7d5a22..3a4716e0b4 100644 --- a/pkg/contracts/ton/gateway_msg.go +++ b/pkg/contracts/ton/gateway_msg.go @@ -24,6 +24,15 @@ const ( const OpWithdraw Op = 200 +// ExitCode represents an error code. Might be TVM or custom. +// TVM: https://docs.ton.org/v3/documentation/tvm/tvm-exit-codes +// Zeta: https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/common/errors.fc +type ExitCode uint32 + +const ( + ExitCodeInvalidSeqno ExitCode = 109 +) + // Donation represents a donation operation type Donation struct { Sender ton.AccountID diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go index 881853241a..092bfa0e9d 100644 --- a/zetaclient/chains/ton/observer/inbound.go +++ b/zetaclient/chains/ton/observer/inbound.go @@ -19,32 +19,40 @@ import ( ) const ( - // MaxTransactionsPerTick is the maximum number of transactions to process on a ticker - MaxTransactionsPerTick = 100 + // maximum number of transactions to process on a ticker + // TODO: move to config + // https://github.com/zeta-chain/node/issues/3086 + maxTransactionsPerTick = 100 + // zero log sample rate for sampled logger (to avoid spamming logs) + logSampleRate = 10 ) // watchInbound watches for new txs to Gateway's account. func (ob *Observer) watchInbound(ctx context.Context) error { + return ob.inboundTicker(ctx, "WatchInbound", ob.observeGateway) +} + +func (ob *Observer) watchInboundTracker(ctx context.Context) error { + return ob.inboundTicker(ctx, "WatchInboundTracker", ob.processInboundTrackers) +} + +func (ob *Observer) inboundTicker(ctx context.Context, taskName string, taskFunc func(context.Context) error) error { app, err := zctx.FromContext(ctx) if err != nil { return err } - var ( - initialInterval = ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) - sampledLogger = ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10}) - ) - - ob.Logger().Inbound.Info().Msgf("WatchInbound started") + initialInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) + sampledLogger := ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: logSampleRate}) task := func(ctx context.Context, t *ticker.Ticker) error { if !app.IsInboundObservationEnabled() { - sampledLogger.Info().Msg("WatchInbound: inbound observation is disabled") + sampledLogger.Info().Msgf("%s: inbound observation is disabled", taskName) return nil } - if err := ob.observeGateway(ctx); err != nil { - ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeGateway error") + if err := taskFunc(ctx); err != nil { + ob.Logger().Inbound.Err(err).Msgf("%s failed", taskName) } newInterval := ticker.DurationFromUint64Seconds(ob.ChainParams().InboundTicker) @@ -58,7 +66,7 @@ func (ob *Observer) watchInbound(ctx context.Context) error { initialInterval, task, ticker.WithStopChan(ob.StopChannel()), - ticker.WithLogger(ob.Logger().Inbound, "WatchInbound"), + ticker.WithLogger(ob.Logger().Inbound, taskName), ) } @@ -86,11 +94,11 @@ func (ob *Observer) observeGateway(ctx context.Context) error { case len(txs) == 0: // noop return nil - case len(txs) > MaxTransactionsPerTick: + case len(txs) > maxTransactionsPerTick: ob.Logger().Inbound.Info(). - Msgf("observeGateway: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick) + Msgf("observeGateway: got %d transactions. Taking first %d", len(txs), maxTransactionsPerTick) - txs = txs[:MaxTransactionsPerTick] + txs = txs[:maxTransactionsPerTick] default: ob.Logger().Inbound.Info().Msgf("observeGateway: got %d transactions", len(txs)) } @@ -156,6 +164,63 @@ func (ob *Observer) observeGateway(ctx context.Context) error { return nil } +// processInboundTrackers handles adhoc trackers that were somehow missed by +func (ob *Observer) processInboundTrackers(ctx context.Context) error { + trackers, err := ob.ZetacoreClient().GetInboundTrackersForChain(ctx, ob.Chain().ChainId) + if err != nil { + return errors.Wrap(err, "unable to get inbound trackers") + } + + // noop + if len(trackers) == 0 { + return nil + } + + gatewayAccountID := ob.gateway.AccountID() + + // a single error should not block other trackers + for _, tracker := range trackers { + txHash := tracker.TxHash + + lt, hash, err := liteapi.TransactionHashFromString(txHash) + if err != nil { + ob.logSkippedTracker(txHash, "unable_to_parse_hash", err) + continue + } + + raw, err := ob.client.GetTransaction(ctx, gatewayAccountID, lt, hash) + if err != nil { + ob.logSkippedTracker(txHash, "unable_to_get_tx", err) + continue + } + + tx, err := ob.gateway.ParseTransaction(raw) + + switch { + case errors.Is(err, toncontracts.ErrParse) || errors.Is(err, toncontracts.ErrUnknownOp): + ob.logSkippedTracker(txHash, "unrelated_tx", err) + continue + case err != nil: + // should not happen + ob.logSkippedTracker(txHash, "unexpected_error", err) + continue + case tx.ExitCode != 0: + ob.logSkippedTracker(txHash, "failed_tx", nil) + continue + case tx.IsOutbound(): + ob.logSkippedTracker(txHash, "outbound_tx", nil) + continue + } + + if _, err := ob.voteInbound(ctx, tx); err != nil { + ob.logSkippedTracker(txHash, "vote_failed", err) + continue + } + } + + return nil +} + // Sends PostVoteInbound to zetacore func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transaction) (string, error) { // noop @@ -283,6 +348,14 @@ func (ob *Observer) setLastScannedTX(tx *toncontracts.Transaction) { Msg("setLastScannedTX: WriteLastTxScannedToDB") } +func (ob *Observer) logSkippedTracker(hash string, reason string, err error) { + ob.Logger().Inbound.Warn(). + Str("transaction.hash", hash). + Str("skip_reason", reason). + Err(err). + Msg("Skipping tracker") +} + func txLogFields(tx *toncontracts.Transaction) map[string]any { return map[string]any{ "transaction.hash": liteapi.TransactionToHashString(tx.Transaction), diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go index 953db3c744..e0b7478cfa 100644 --- a/zetaclient/chains/ton/observer/inbound_test.go +++ b/zetaclient/chains/ton/observer/inbound_test.go @@ -2,6 +2,7 @@ package observer import ( "encoding/hex" + "fmt" "testing" "github.com/pkg/errors" @@ -11,14 +12,11 @@ import ( "github.com/tonkeeper/tongo/ton" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" "github.com/zeta-chain/node/testutil/sample" + cc "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi" ) func TestInbound(t *testing.T) { - gw := toncontracts.NewGateway( - ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b"), - ) - t.Run("No gateway provided", func(t *testing.T) { ts := newTestSuite(t) @@ -32,11 +30,11 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) // Given mocked lite client call - ts.OnGetFirstTransaction(gw.AccountID(), nil, 0, errors.New("oops")).Once() + ts.OnGetFirstTransaction(ts.gateway.AccountID(), nil, 0, errors.New("oops")).Once() // ACT // Observe inbounds once @@ -52,16 +50,16 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given mocked lite client calls - firstTX := sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{ + firstTX := sample.TONDonation(t, ts.gateway.AccountID(), toncontracts.Donation{ Sender: sample.GenerateTONAccountID(), Amount: tonCoins(t, "1"), }) - ts.OnGetFirstTransaction(gw.AccountID(), &firstTX, 0, nil).Once() - ts.OnGetTransactionsSince(gw.AccountID(), firstTX.Lt, txHash(firstTX), nil, nil).Once() + ts.OnGetFirstTransaction(ts.gateway.AccountID(), &firstTX, 0, nil).Once() + ts.OnGetTransactionsSince(ts.gateway.AccountID(), firstTX.Lt, txHash(firstTX), nil, nil).Once() // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) // ACT @@ -88,13 +86,13 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) - lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + lastScanned := ts.SetupLastScannedTX(ts.gateway.AccountID()) // Given mocked lite client calls - donation := sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{ + donation := sample.TONDonation(t, ts.gateway.AccountID(), toncontracts.Donation{ Sender: sample.GenerateTONAccountID(), Amount: tonCoins(t, "12"), }) @@ -102,7 +100,7 @@ func TestInbound(t *testing.T) { txs := []ton.Transaction{donation} ts. - OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + OnGetTransactionsSince(ts.gateway.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). Once() // ACT @@ -124,10 +122,10 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) - lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + lastScanned := ts.SetupLastScannedTX(ts.gateway.AccountID()) // Given mocked lite client calls deposit := toncontracts.Deposit{ @@ -136,11 +134,11 @@ func TestInbound(t *testing.T) { Recipient: sample.EthAddress(), } - depositTX := sample.TONDeposit(t, gw.AccountID(), deposit) + depositTX := sample.TONDeposit(t, ts.gateway.AccountID(), deposit) txs := []ton.Transaction{depositTX} ts. - OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + OnGetTransactionsSince(ts.gateway.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). Once() ts.MockGetBlockHeader(depositTX.BlockID) @@ -182,10 +180,10 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) - lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + lastScanned := ts.SetupLastScannedTX(ts.gateway.AccountID()) // Given mocked lite client calls const callData = "hey there" @@ -198,11 +196,11 @@ func TestInbound(t *testing.T) { CallData: []byte(callData), } - depositAndCallTX := sample.TONDepositAndCall(t, gw.AccountID(), depositAndCall) + depositAndCallTX := sample.TONDepositAndCall(t, ts.gateway.AccountID(), depositAndCall) txs := []ton.Transaction{depositAndCallTX} ts. - OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + OnGetTransactionsSince(ts.gateway.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). Once() ts.MockGetBlockHeader(depositAndCallTX.BlockID) @@ -251,10 +249,10 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) - lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + lastScanned := ts.SetupLastScannedTX(ts.gateway.AccountID()) // Given mocked lite client calls withdrawal := toncontracts.Withdrawal{ @@ -269,11 +267,11 @@ func TestInbound(t *testing.T) { require.NoError(t, err) require.Equal(t, ob.TSS().EVMAddress().Hex(), withdrawalSigner.Hex()) - withdrawalTX := sample.TONWithdrawal(t, gw.AccountID(), withdrawal) + withdrawalTX := sample.TONWithdrawal(t, ts.gateway.AccountID(), withdrawal) txs := []ton.Transaction{withdrawalTX} ts. - OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + OnGetTransactionsSince(ts.gateway.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). Once() // ACT @@ -299,10 +297,10 @@ func TestInbound(t *testing.T) { ts := newTestSuite(t) // Given observer - ob, err := New(ts.baseObserver, ts.liteClient, gw) + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) require.NoError(t, err) - lastScanned := ts.SetupLastScannedTX(gw.AccountID()) + lastScanned := ts.SetupLastScannedTX(ts.gateway.AccountID()) // Given several transactions withdrawal := toncontracts.Withdrawal{ @@ -314,39 +312,39 @@ func TestInbound(t *testing.T) { txs := []ton.Transaction{ // should be skipped - sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{ + sample.TONDonation(t, ts.gateway.AccountID(), toncontracts.Donation{ Sender: sample.GenerateTONAccountID(), Amount: tonCoins(t, "1"), }), // should be voted - sample.TONDeposit(t, gw.AccountID(), toncontracts.Deposit{ + sample.TONDeposit(t, ts.gateway.AccountID(), toncontracts.Deposit{ Sender: sample.GenerateTONAccountID(), Amount: tonCoins(t, "3"), Recipient: sample.EthAddress(), }), // should be skipped (invalid inbound message) sample.TONTransaction(t, sample.TONTransactionProps{ - Account: gw.AccountID(), + Account: ts.gateway.AccountID(), Input: &tlb.Message{}, }), // should be voted - sample.TONDeposit(t, gw.AccountID(), toncontracts.Deposit{ + sample.TONDeposit(t, ts.gateway.AccountID(), toncontracts.Deposit{ Sender: sample.GenerateTONAccountID(), Amount: tonCoins(t, "3"), Recipient: sample.EthAddress(), }), // a tracker should be added - sample.TONWithdrawal(t, gw.AccountID(), withdrawal), + sample.TONWithdrawal(t, ts.gateway.AccountID(), withdrawal), // should be skipped (invalid inbound/outbound messages) sample.TONTransaction(t, sample.TONTransactionProps{ - Account: gw.AccountID(), + Account: ts.gateway.AccountID(), Input: &tlb.Message{}, Output: &tlb.Message{}, }), } ts. - OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). + OnGetTransactionsSince(ts.gateway.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil). Once() for _, tx := range txs { @@ -391,6 +389,58 @@ func TestInbound(t *testing.T) { }) } +func TestInboundTracker(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + + // Given observer + ob, err := New(ts.baseObserver, ts.liteClient, ts.gateway) + require.NoError(t, err) + + // Given TON gateway transactions + // should be voted + deposit := toncontracts.Deposit{ + Sender: sample.GenerateTONAccountID(), + Amount: toncontracts.Coins(5), + Recipient: sample.EthAddress(), + } + + txDeposit := sample.TONDeposit(t, ts.gateway.AccountID(), deposit) + ts.MockGetTransaction(ts.gateway.AccountID(), txDeposit) + ts.MockGetBlockHeader(txDeposit.BlockID) + + // Should be skipped (I doubt anyone would vote for this gov proposal, but let’s still put up rail guards) + txWithdrawal := sample.TONWithdrawal(t, ts.gateway.AccountID(), toncontracts.Withdrawal{ + Recipient: sample.GenerateTONAccountID(), + Amount: toncontracts.Coins(5), + Seqno: 1, + }) + ts.MockGetTransaction(ts.gateway.AccountID(), txWithdrawal) + ts.MockGetBlockHeader(txWithdrawal.BlockID) + + // Given inbound trackers from zetacore + trackers := []cc.InboundTracker{ + ts.TxToInboundTracker(txDeposit), + ts.TxToInboundTracker(txWithdrawal), + } + + ts.OnGetInboundTrackersForChain(trackers).Once() + + // ACT + err = ob.processInboundTrackers(ts.ctx) + + // ARRANGE + require.NoError(t, err) + require.Len(t, ts.votesBag, 1) + + vote := ts.votesBag[0] + assert.Equal(t, deposit.Amount, vote.Amount) + assert.Equal(t, deposit.Sender.ToRaw(), vote.Sender) + + // zevm recipient bytes == memo bytes + assert.Equal(t, fmt.Sprintf("%x", deposit.Recipient), vote.Message) +} + func txHash(tx ton.Transaction) ton.Bits256 { return ton.Bits256(tx.Hash()) } diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go index 41523d5759..565a544073 100644 --- a/zetaclient/chains/ton/observer/observer.go +++ b/zetaclient/chains/ton/observer/observer.go @@ -86,17 +86,16 @@ func (ob *Observer) Start(ctx context.Context) { ob.Logger().Chain.Info().Msg("observer is starting") - start := func(job func(ctx context.Context) error, name string, log zerolog.Logger) { - bg.Work(ctx, job, bg.WithName(name), bg.WithLogger(log)) - } - - // TODO: watchInboundTracker - // https://github.com/zeta-chain/node/issues/2935 + start(ctx, ob.watchInbound, "WatchInbound", ob.Logger().Inbound) + start(ctx, ob.watchInboundTracker, "WatchInboundTracker", ob.Logger().Inbound) + start(ctx, ob.watchOutbound, "WatchOutbound", ob.Logger().Outbound) + start(ctx, ob.watchGasPrice, "WatchGasPrice", ob.Logger().GasPrice) + start(ctx, ob.watchRPCStatus, "WatchRPCStatus", ob.Logger().Chain) +} - start(ob.watchInbound, "WatchInbound", ob.Logger().Inbound) - start(ob.watchOutbound, "WatchOutbound", ob.Logger().Outbound) - start(ob.watchGasPrice, "WatchGasPrice", ob.Logger().GasPrice) - start(ob.watchRPCStatus, "WatchRPCStatus", ob.Logger().Chain) +// fire goroutine task +func start(ctx context.Context, task func(ctx context.Context) error, name string, log zerolog.Logger) { + bg.Work(ctx, task, bg.WithName(name), bg.WithLogger(log)) } // watchGasPrice observes TON gas price and votes it to Zetacore. diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go index 502a269267..290e34081a 100644 --- a/zetaclient/chains/ton/observer/observer_test.go +++ b/zetaclient/chains/ton/observer/observer_test.go @@ -13,6 +13,7 @@ import ( "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" "github.com/zeta-chain/node/testutil/sample" cc "github.com/zeta-chain/node/x/crosschain/types" @@ -31,6 +32,7 @@ type testSuite struct { chain chains.Chain chainParams *observertypes.ChainParams + gateway *toncontracts.Gateway liteClient *mocks.LiteClient zetacore *mocks.ZetacoreClient @@ -55,6 +57,10 @@ func newTestSuite(t *testing.T) *testSuite { chain = chains.TONTestnet chainParams = sample.ChainParams(chain.ChainId) + gateway = toncontracts.NewGateway(ton.MustParseAccountID( + "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b", + )) + liteClient = mocks.NewLiteClient(t) tss = mocks.NewGeneratedTSS(t, chain) @@ -90,6 +96,7 @@ func newTestSuite(t *testing.T) *testSuite { chainParams: chainParams, liteClient: liteClient, + gateway: gateway, zetacore: zetacore, tss: tss, @@ -168,6 +175,20 @@ func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call { Return(blockInfo, nil) } +func (ts *testSuite) OnGetInboundTrackersForChain(trackers []cc.InboundTracker) *mock.Call { + return ts.zetacore. + On("GetInboundTrackersForChain", mock.Anything, ts.chain.ChainId). + Return(trackers, nil) +} + +func (ts *testSuite) TxToInboundTracker(tx ton.Transaction) cc.InboundTracker { + return cc.InboundTracker{ + ChainId: ts.chain.ChainId, + TxHash: liteapi.TransactionToHashString(tx), + CoinType: coin.CoinType_Gas, + } +} + type signable interface { Hash() ([32]byte, error) SetSignature([65]byte) diff --git a/zetaclient/chains/ton/signer/signer.go b/zetaclient/chains/ton/signer/signer.go index 7542fb37f6..bdd25c0c18 100644 --- a/zetaclient/chains/ton/signer/signer.go +++ b/zetaclient/chains/ton/signer/signer.go @@ -2,10 +2,14 @@ package signer import ( "context" + "regexp" + "strconv" + "strings" ethcommon "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru" "github.com/pkg/errors" + "github.com/tonkeeper/tongo/liteclient" "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" @@ -84,13 +88,20 @@ func (s *Signer) TryProcessOutbound( }() outcome, err := s.ProcessOutbound(ctx, cctx, zetacore, zetaBlockHeight) - if err != nil { - s.Logger().Std.Error(). - Err(err). - Str("outbound.id", outboundID). - Uint64("outbound.nonce", cctx.GetCurrentOutboundParam().TssNonce). - Str("outbound.outcome", string(outcome)). - Msg("Unable to ProcessOutbound") + + lf := map[string]any{ + "outbound.id": outboundID, + "outbound.nonce": cctx.GetCurrentOutboundParam().TssNonce, + "outbound.outcome": string(outcome), + } + + switch { + case err != nil: + s.Logger().Std.Error().Err(err).Fields(lf).Msg("Unable to ProcessOutbound") + case outcome != Success: + s.Logger().Std.Warn().Fields(lf).Msg("Unsuccessful outcome for ProcessOutbound") + default: + s.Logger().Std.Info().Fields(lf).Msg("Processed outbound") } } @@ -147,16 +158,8 @@ func (s *Signer) ProcessOutbound( // Example: If a cctx has amount of 5 TON, the recipient will receive 5 TON, // and gateway's balance will be decreased by 5 TON + txFees. exitCode, err := s.gateway.SendExternalMessage(ctx, s.client, withdrawal) - switch { - case err != nil: - return Fail, errors.Wrap(err, "unable to send external message") - case exitCode != 0: - // Might happen if msg's nonce is too high; retry later. - lf["outbound.exit_code"] = exitCode - lf["outbound.outcome"] = string(Invalid) - s.Logger().Std.Info().Fields(lf).Msg("Unable to send external message") - - return Invalid, nil + if err != nil || exitCode != 0 { + return s.handleSendError(exitCode, err, lf) } // it's okay to run this in the same goroutine @@ -211,6 +214,62 @@ func (s *Signer) setSignature(hash [32]byte, sig [65]byte) { s.signaturesCache.Add(hash, sig) } +// Sample (from local ton): +// error code: 0 message: cannot apply external message to current state: +// External message was not accepted Cannot run message on account: +// inbound external message rejected by transaction ...: exitcode=109, steps=108, gas_used=0\ +// VM Log (truncated): ... +var exitCodeErrorRegex = regexp.MustCompile(`exitcode=(\d+)`) + +// handleSendError tries to figure out the reason of the send error. +func (s *Signer) handleSendError(exitCode uint32, err error, logFields map[string]any) (Outcome, error) { + if err != nil { + // Might be possible if 2 concurrent zeta clients + // are trying to broadcast the same message. + if strings.Contains(err.Error(), "duplicate message") { + s.Logger().Std.Warn().Fields(logFields).Msg("Message already sent") + return Invalid, nil + } + + var errLiteClient liteclient.LiteServerErrorC + if errors.As(err, &errLiteClient) { + logFields["outbound.error.message"] = errLiteClient.Message + exitCode = errLiteClient.Code + } + + if code, ok := extractExitCode(err.Error()); ok { + exitCode = code + } + } + + switch { + case exitCode == uint32(toncontracts.ExitCodeInvalidSeqno): + // Might be possible if zeta clients send several seq. numbers concurrently. + // In the current implementation, Gateway supports only 1 nonce per block. + logFields["outbound.error.exit_code"] = exitCode + s.Logger().Std.Warn().Fields(logFields).Msg("Invalid nonce, retry later") + return Invalid, nil + case err != nil: + return Fail, errors.Wrap(err, "unable to send external message") + default: + return Fail, errors.Errorf("unable to send external message: exit code %d", exitCode) + } +} + +func extractExitCode(text string) (uint32, bool) { + match := exitCodeErrorRegex.FindStringSubmatch(text) + if len(match) < 2 { + return 0, false + } + + exitCode, err := strconv.ParseUint(match[1], 10, 32) + if err != nil { + return 0, false + } + + return uint32(exitCode), true +} + // GetGatewayAddress returns gateway address as raw TON address "0:ABC..." func (s *Signer) GetGatewayAddress() string { return s.gateway.AccountID().ToRaw() diff --git a/zetaclient/chains/ton/signer/signer_test.go b/zetaclient/chains/ton/signer/signer_test.go index 9ac85f281a..dcd78c9817 100644 --- a/zetaclient/chains/ton/signer/signer_test.go +++ b/zetaclient/chains/ton/signer/signer_test.go @@ -85,6 +85,31 @@ func TestSigner(t *testing.T) { require.Equal(t, liteapi.TransactionToHashString(withdrawalTX), tracker.hash) } +func TestExitCodeRegex(t *testing.T) { + for _, tt := range []string{ + `unable to send external message: error code: 0 message: + cannot apply external message to current state : + External message was not accepted\nCannot run message on account: inbound external message rejected by + transaction CC8803E21EDA7E6487D191380725A82CD75316E1C131496E1A5636751CE60347: + \nexitcode=109, steps=108, gas_used=0\nVM Log (truncated):\n...INT 0\nexecute THROWIFNOT + 105\nexecute MYADDR\nexecute XCHG s1,s4\nexecute SDEQ\nexecute THROWIF 112\nexecute OVER\nexecute + EQINT 0\nexecute THROWIF 106\nexecute GETGLOB + 3\nexecute NEQ\nexecute THROWIF 109\ndefault exception handler, terminating vm with exit code 109\n`, + + `unable to send external message: error code: 0 message: cannot apply external message to current state : + External message was not accepted\nCannot run message on account: + inbound external message rejected by transaction + 6CCBB83C7D9BFBFDB40541F35AD069714856F18B4850C1273A117DF6BFADE1C6:\nexitcode=109, steps=108, + gas_used=0\nVM Log (truncated):\n...INT 0....`, + } { + require.True(t, exitCodeErrorRegex.MatchString(tt)) + + exitCode, ok := extractExitCode(tt) + require.True(t, ok) + require.Equal(t, uint32(109), exitCode) + } +} + type testSuite struct { ctx context.Context t *testing.T diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index b632b9234a..fe1c26b76a 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -756,7 +756,12 @@ func (oc *Orchestrator) ScheduleCCTXTON( } // fire async task - taskLogger := oc.logger.Logger.With().Str("outbound.id", outboundID).Logger() + taskLogger := oc.logger.Logger.With(). + Int64(logs.FieldChain, chainID). + Str("outbound.id", outboundID). + Uint64("outbound.nonce", cctx.GetCurrentOutboundParam().TssNonce). + Logger() + bg.Work(ctx, task, bg.WithName("TryProcessOutbound"), bg.WithLogger(taskLogger)) } } From 55c01c204db0597e858ea8c1e9813b98e1fc6c37 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 5 Nov 2024 12:37:59 -0800 Subject: [PATCH 29/34] chore: upgrade go-ethereum (#3031) * tolerate go-etehreum changes tolerate 1.13 changes fix eth proof trie initilization fix go-ethereum spc lint fix final pre merge version * use final merged version --- app/ante/interfaces.go | 2 +- contrib/rpcimportable/go.mod | 5 + e2e/runner/setup_evm.go | 2 +- go.mod | 42 +-- go.sum | 246 +++++++++--------- pkg/proofs/ethereum/proof.go | 11 +- precompiles/bank/method_test.go | 3 +- precompiles/precompiles.go | 6 +- precompiles/staking/method_stake.go | 10 +- precompiles/staking/staking_test.go | 5 +- precompiles/types/address_test.go | 5 +- rpc/backend/backend.go | 3 +- rpc/backend/chain_info.go | 3 +- rpc/backend/chain_info_test.go | 3 +- rpc/backend/sign_tx.go | 4 +- rpc/backend/utils.go | 4 +- rpc/namespaces/ethereum/debug/api.go | 13 - rpc/namespaces/ethereum/eth/api.go | 5 +- server/json_rpc.go | 54 +++- testutil/keeper/mocks/fungible/evm.go | 8 +- testutil/sample/crypto.go | 2 +- x/crosschain/keeper/evm_hooks.go | 10 +- .../keeper/msg_server_vote_inbound_tx_test.go | 3 +- x/fungible/keeper/evm.go | 29 ++- x/fungible/keeper/evm_hooks.go | 2 +- .../keeper/zevm_message_passing_test.go | 9 +- x/fungible/types/expected_keepers.go | 2 +- 27 files changed, 275 insertions(+), 216 deletions(-) diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index ffe1090121..d0afe126a3 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -41,7 +41,7 @@ type EVMKeeper interface { statedb.Keeper DynamicFeeEVMKeeper - NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM + NewEVM(ctx sdk.Context, msg *core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error GetBalance(ctx sdk.Context, addr common.Address) *big.Int ResetTransientGasUsed(ctx sdk.Context) diff --git a/contrib/rpcimportable/go.mod b/contrib/rpcimportable/go.mod index 32fe6d9e3d..044f51ae8a 100644 --- a/contrib/rpcimportable/go.mod +++ b/contrib/rpcimportable/go.mod @@ -11,5 +11,10 @@ replace ( github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 ) +// go-ethereum fork must be used as it removes incompatible pebbledb version +replace ( + github.com/ethereum/go-ethereum => github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc +) + // uncomment this for local development/testing/debugging // replace github.com/zeta-chain/node => ../.. \ No newline at end of file diff --git a/e2e/runner/setup_evm.go b/e2e/runner/setup_evm.go index 733109fbf6..f14383356b 100644 --- a/e2e/runner/setup_evm.go +++ b/e2e/runner/setup_evm.go @@ -76,7 +76,7 @@ func (r *E2ERunner) SetupEVM(contractsDeployed bool, whitelistERC20 bool) { r.ZetaEth = ZetaEth r.ZetaEthAddr = zetaEthAddr conf.Contracts.EVM.ZetaEthAddr = config.DoubleQuotedString(zetaEthAddr.String()) - r.Logger.Info("ZetaEth contract address: %s, tx hash: %s", zetaEthAddr.Hex(), zetaEthAddr.Hash().Hex()) + r.Logger.Info("ZetaEth contract address: %s, tx hash: %s", zetaEthAddr.Hex(), txZetaEth.Hash()) r.Logger.Info("Deploying ZetaConnectorEth contract") connectorEthAddr, txConnector, ConnectorEth, err := zetaconnectoreth.DeployZetaConnectorEth( diff --git a/go.mod b/go.mod index c92e2d2767..e1b73c75c8 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/cosmos/ibc-go/v7 v7.4.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/emicklei/proto v1.11.1 - github.com/ethereum/go-ethereum v1.10.26 + github.com/ethereum/go-ethereum v1.13.15 github.com/fatih/color v1.14.1 github.com/frumioj/crypto11 v1.2.5-0.20210823151709-946ce662cc0e github.com/gagliardetto/solana-go v1.10.0 @@ -57,13 +57,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 - github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7 + github.com/zeta-chain/ethermint v0.0.0-20241105191054-1ebf85a354a0 github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c gitlab.com/thorchain/tss/go-tss v1.6.5 go.nhat.io/grpcmock v0.25.0 golang.org/x/crypto v0.23.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/net v0.25.0 golang.org/x/sync v0.7.0 google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 @@ -91,7 +91,7 @@ require ( github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/DataDog/zstd v1.5.0 // indirect github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/agl/ed25519 v0.0.0-20200225211852-fd4d107ace12 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.4.1 // indirect @@ -135,10 +135,9 @@ require ( github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 // indirect + github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect - github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -152,16 +151,15 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -186,8 +184,8 @@ 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.3 // indirect - github.com/huin/goupnp v1.2.0 // indirect + github.com/holiman/uint256 v1.2.4 + github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.10.0 // indirect @@ -226,7 +224,7 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect 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-runewidth v0.0.13 // 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 @@ -265,10 +263,8 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/prometheus/tsdb v0.7.1 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rjeczalik/notify v0.9.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect @@ -325,7 +321,6 @@ require ( google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect lukechampine.com/blake3 v1.2.1 // indirect nhooyr.io/websocket v1.8.7 // indirect pgregory.net/rapid v1.1.0 // indirect @@ -341,16 +336,31 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/aead/siphash v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/snksoft/crc v1.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/supranational/blst v0.3.11 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace ( @@ -369,7 +379,7 @@ replace ( // https://github.com/zeta-chain/tss-lib/tree/threshold-dep-updates // which is a fork of https://github.com/threshold-network/tss-lib github.com/bnb-chain/tss-lib => github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901 - github.com/ethereum/go-ethereum => github.com/zeta-chain/go-ethereum v1.10.26-spc + github.com/ethereum/go-ethereum => github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc github.com/libp2p/go-libp2p => github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 gitlab.com/thorchain/tss/go-tss => github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 ) diff --git a/go.sum b/go.sum index 404160ddd4..2d635ec666 100644 --- a/go.sum +++ b/go.sum @@ -11,7 +11,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -233,7 +232,6 @@ cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6Pm cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -1192,7 +1190,6 @@ cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCw cloud.google.com/go/workflows v1.12.2/go.mod h1:+OmBIgNqYJPVggnMo9nqmizW0qEXHhmnAzK/CnBqsHc= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= @@ -1259,11 +1256,19 @@ github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0/go.mod h1:ceIuwmxDWptoW3eCqSXlnPsZFKh4X+R38dWPv7GS9Vs= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -1308,11 +1313,14 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= @@ -1323,7 +1331,6 @@ github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mo github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -1406,8 +1413,8 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= @@ -1445,7 +1452,6 @@ github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKS github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -1455,7 +1461,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= @@ -1504,18 +1509,22 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= +github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= +github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= +github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2/go.mod h1:TQZBt/WaQy+zTHoW++rnl8JBrmZ0VO6EUbVua1+foCA= +github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -1538,6 +1547,10 @@ github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43 h1:Vkf7 github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43/go.mod h1:TnVqVdGEK8b6erOMkcyYGWzCQMw7HEMCOw3BgFYCFWs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= @@ -1551,8 +1564,6 @@ github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHf github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= @@ -1626,7 +1637,6 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -1661,12 +1671,15 @@ github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAc github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= @@ -1682,7 +1695,7 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.1/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/cloudflare/cloudflare-go v0.79.0/go.mod h1:gkHQf9xEubaQPEuerBuoinR9P8bf8a05Lq0X6WKy1Oc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -1745,10 +1758,13 @@ github.com/cometbft/cometbft v0.37.5/go.mod h1:QC+mU0lBhKn8r9qvmnq53Dmf3DWBt4Vtk github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/cometbft/cometbft-db v0.12.0 h1:v77/z0VyfSU7k682IzZeZPFZrQAKiQwkqGN0QzAjMi0= github.com/cometbft/cometbft-db v0.12.0/go.mod h1:aX2NbCrjNVd2ZajYxt1BsiFf/Z+TQ2MN0VxdicheYuw= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= +github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -1943,6 +1959,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= @@ -1968,7 +1988,6 @@ github.com/daixiang0/gci v0.8.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+M github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1980,6 +1999,8 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= @@ -1991,7 +2012,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= @@ -2011,7 +2031,6 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -2042,7 +2061,6 @@ github.com/docker/docker v0.0.0-20200511152416-a93e9eb0e95c/go.mod h1:eEKB0N0r5N github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.3-0.20211208011758-87521affb077+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -2066,9 +2084,8 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNE github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 h1:kgvzE5wLsLa7XKfV85VZl40QXaMCaeFtHpPwJ8fhotY= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= +github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= +github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -2081,8 +2098,6 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= @@ -2123,6 +2138,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -2147,11 +2164,12 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= github.com/firefart/nonamedreturns v1.0.1/go.mod h1:D3dpIBojGGNh5UfElmwPu73SwDCm+VKhHYqwlNOk2uQ= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -2198,8 +2216,9 @@ github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYis github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -2220,8 +2239,6 @@ github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -2282,8 +2299,9 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -2320,7 +2338,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -2359,8 +2376,9 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -2373,8 +2391,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -2387,16 +2405,18 @@ github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2 github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= @@ -2447,8 +2467,9 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= @@ -2486,7 +2507,6 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= @@ -2523,7 +2543,6 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= @@ -2556,6 +2575,7 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -2567,6 +2587,7 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -2737,6 +2758,7 @@ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= @@ -2781,11 +2803,12 @@ github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= 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.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= 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= @@ -2796,9 +2819,8 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= @@ -2808,6 +2830,7 @@ github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -2823,19 +2846,11 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/informalsystems/tm-load-test v1.0.0/go.mod h1:WVaSKaQdfZK3v0C74EMzn7//+3aeCZF8wkIKBz2/M74= github.com/informalsystems/tm-load-test v1.3.0/go.mod h1:OQ5AQ9TbT5hKWBNIwsMjn6Bf4O0U4b1kRc+0qZlQJKw= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= @@ -2884,7 +2899,7 @@ github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPw github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -2944,7 +2959,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= @@ -2956,7 +2970,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kataras/blocks v0.0.6/go.mod h1:UK+Iwk0Oxpc0GdoJja7sEildotAUKK1LYeYcVF0COWc= @@ -2983,6 +2996,7 @@ github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIR github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= @@ -3018,15 +3032,13 @@ github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -3055,6 +3067,7 @@ github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfR github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= @@ -3067,6 +3080,7 @@ github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= github.com/ldez/tagliatelle v0.3.1/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -3194,20 +3208,19 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -3258,6 +3271,7 @@ github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= @@ -3281,6 +3295,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -3291,6 +3306,9 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.10.3/go.mod h1:jxeOuly98l9gWHai0Ojrbnczrk/rf+o9/JqNhY+UCSo= github.com/moby/buildkit v0.10.4/go.mod h1:Yajz9vt1Zw5q9Pp4pdb3TCSUXJBIroIQGQ3TTs/sLug= @@ -3325,6 +3343,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= @@ -3341,7 +3361,6 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= @@ -3536,7 +3555,6 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -3563,7 +3581,6 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -3585,13 +3602,11 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -3603,6 +3618,7 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -3616,7 +3632,6 @@ github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdL github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -3699,8 +3714,9 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= @@ -3736,9 +3752,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq github.com/remyoudompheng/go-dbus v0.0.0-20121104212943-b7232d34b1d5/go.mod h1:+u151txRmLpwxBmpYn9z3d1sdJdjRPQpsXuYeY9jNls= github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls= github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= -github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -3815,8 +3830,6 @@ github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfF github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/securego/gosec/v2 v2.13.1/go.mod h1:EO1sImBMBWFjOTFzMWfTRrZW6M15gm60ljzrmy/wtHo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -3934,7 +3947,6 @@ github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+eg github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= @@ -3961,7 +3973,6 @@ github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRci github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -3985,7 +3996,8 @@ github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7 github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= @@ -4047,18 +4059,15 @@ github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiff github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timonwong/loggercheck v0.9.3/go.mod h1:wUqnk9yAOIKtGA39l1KLE9Iz0QiTocu/YZoOf+OzFdw= github.com/timonwong/logrlint v0.1.0/go.mod h1:Zleg4Gw+kRxNej+Ra7o+tEaW5k1qthTaYKU7rSD39LU= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= 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/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -4086,7 +4095,6 @@ github.com/tonkeeper/tongo v1.9.3/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhK github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= @@ -4117,9 +4125,9 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= -github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= @@ -4156,7 +4164,6 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvS github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= @@ -4173,7 +4180,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -4204,10 +4210,10 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7 h1:eW5aAW9Ag4GDMa7qzsQm6EWC6SENQUokHUpCdS+WSSg= -github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7/go.mod h1:bY9wUmkSjTJ65U7LF3e9Pc2737NqxCXGN+b/U2Rm5rU= -github.com/zeta-chain/go-ethereum v1.10.26-spc h1:NvY4rR9yw52wfxWt7YoFsWbaIwVMyOtTsWKqGAXk+sE= -github.com/zeta-chain/go-ethereum v1.10.26-spc/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= +github.com/zeta-chain/ethermint v0.0.0-20241105191054-1ebf85a354a0 h1:Mr6EEv9H0Ac9kpG/OnYz3nt0Uh48JiVwdDu1HLJlPBs= +github.com/zeta-chain/ethermint v0.0.0-20241105191054-1ebf85a354a0/go.mod h1:e1G1pfDM9is8ZrskMPw2oSuITBU7+vSdfxTZyHbzy8A= +github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc h1:FVOttT/f7QCZMkOLssLTY1cbX8pT+HS/kg81zgUAmYE= +github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc/go.mod h1:MgO2/CmxFnj6W7v/5hrz3ypco3kHkb8856pRnFkY4xQ= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 h1:FmO3HfVdZ7LzxBUfg6sVzV7ilKElQU2DZm8PxJ7KcYI= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4/go.mod h1:TBv5NY/CqWYIfUstXO1fDWrt4bDoqgCw79yihqBspg8= github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 h1:dBwx99+oymiyecnRGu1dnkJmYn2SAgBexBJ6nsdJt+E= @@ -4360,6 +4366,7 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= +go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= @@ -4381,7 +4388,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= @@ -4412,7 +4418,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -4448,6 +4453,7 @@ golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -4467,7 +4473,9 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -4495,8 +4503,8 @@ golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZ golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -4550,6 +4558,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -4615,7 +4625,6 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -4669,8 +4678,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= @@ -4739,6 +4750,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -4747,7 +4759,6 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -4797,7 +4808,6 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -4835,6 +4845,7 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -4853,7 +4864,6 @@ golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -4946,6 +4956,7 @@ golang.org/x/sys v0.10.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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= @@ -4972,6 +4983,7 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= @@ -5077,7 +5089,6 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -5162,6 +5173,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -5174,15 +5187,12 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= @@ -5299,7 +5309,6 @@ google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -5308,7 +5317,6 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= @@ -5646,9 +5654,8 @@ gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -5863,6 +5870,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= diff --git a/pkg/proofs/ethereum/proof.go b/pkg/proofs/ethereum/proof.go index 8f7d2976b9..96a31a1957 100644 --- a/pkg/proofs/ethereum/proof.go +++ b/pkg/proofs/ethereum/proof.go @@ -136,7 +136,7 @@ func (t *Trie) GenerateProof(txIndex int) (*Proof, error) { // #nosec G115 checked as non-negative indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(txIndex)) proof := NewProof() - err := t.Prove(indexBuf, 0, proof) + err := t.Prove(indexBuf, proof) if err != nil { return nil, err } @@ -146,8 +146,7 @@ func (t *Trie) GenerateProof(txIndex int) (*Proof, error) { // NewTrie builds a trie from a DerivableList. The DerivableList must be types.Transactions // or types.Receipts. func NewTrie(list types.DerivableList) Trie { - hasher := new(trie.Trie) - hasher.Reset() + hasher := trie.NewEmpty(nil) valueBuf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(valueBuf) @@ -160,18 +159,18 @@ func NewTrie(list types.DerivableList) Trie { // #nosec G115 iterator indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) value := encodeForDerive(list, i, valueBuf) - hasher.Update(indexBuf, value) + _ = hasher.Update(indexBuf, value) } if list.Len() > 0 { indexBuf = rlp.AppendUint64(indexBuf[:0], 0) value := encodeForDerive(list, 0, valueBuf) - hasher.Update(indexBuf, value) + _ = hasher.Update(indexBuf, value) } for i := 0x80; i < list.Len(); i++ { // #nosec G115 iterator indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) value := encodeForDerive(list, i, valueBuf) - hasher.Update(indexBuf, value) + _ = hasher.Update(indexBuf, value) } return Trie{hasher} } diff --git a/precompiles/bank/method_test.go b/precompiles/bank/method_test.go index 38fc715af6..d820d38f71 100644 --- a/precompiles/bank/method_test.go +++ b/precompiles/bank/method_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" ethermint "github.com/zeta-chain/ethermint/types" evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" @@ -550,7 +551,7 @@ func setupChain(t *testing.T) testSuite { mockVMContract := vm.NewContract( contractRef{address: common.Address{}}, contractRef{address: ContractAddress}, - big.NewInt(0), + uint256.NewInt(0), 0, ) diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index b9d167dbac..24d574695d 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -39,7 +39,7 @@ func StatefulContracts( // Define the prototype contract function. if EnabledStatefulContracts[prototype.ContractAddress] { - prototypeContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + prototypeContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.StatefulPrecompiledContract { return prototype.NewIPrototypeContract(fungibleKeeper, cdc, gasConfig) } @@ -49,7 +49,7 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { - stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.StatefulPrecompiledContract { return staking.NewIStakingContract(ctx, stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) } @@ -58,7 +58,7 @@ func StatefulContracts( } if EnabledStatefulContracts[bank.ContractAddress] { - bankContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + bankContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.StatefulPrecompiledContract { return bank.NewIBankContract(ctx, bankKeeper, *fungibleKeeper, cdc, gasConfig) } diff --git a/precompiles/staking/method_stake.go b/precompiles/staking/method_stake.go index c19fa6c618..90a0affb11 100644 --- a/precompiles/staking/method_stake.go +++ b/precompiles/staking/method_stake.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + "github.com/holiman/uint256" precompiletypes "github.com/zeta-chain/node/precompiles/types" ) @@ -67,12 +68,19 @@ func (c *Contract) Stake( return nil, err } + amountUint256, overflowed := uint256.FromBig(amount) + if overflowed { + return nil, precompiletypes.ErrInvalidArgument{ + Got: args[2], + } + } + // if caller is not the same as origin it means call is coming through smart contract, // and because state of smart contract calling precompile might be updated as well // manually reduce amount in stateDB, so it is properly reflected in bank module stateDB := evm.StateDB.(precompiletypes.ExtStateDB) if contract.CallerAddress != evm.Origin { - stateDB.SubBalance(stakerAddress, amount) + stateDB.SubBalance(stakerAddress, amountUint256) } err = c.addStakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index b7b00ede0d..977090e4dd 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -8,6 +8,7 @@ import ( tmdb "github.com/cometbft/cometbft-db" "github.com/cosmos/cosmos-sdk/store" + "github.com/holiman/uint256" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -262,7 +263,7 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v mockVMContract := vm.NewContract( contractRef{address: common.Address{}}, contractRef{address: ContractAddress}, - big.NewInt(0), + uint256.NewInt(0), 0, ) @@ -338,7 +339,7 @@ func newTestSuite(t *testing.T) testSuite { mockVMContract := vm.NewContract( contractRef{address: common.Address{}}, contractRef{address: ContractAddress}, - big.NewInt(0), + uint256.NewInt(0), 0, ) diff --git a/precompiles/types/address_test.go b/precompiles/types/address_test.go index 801df4f8d4..81dd15b1bd 100644 --- a/precompiles/types/address_test.go +++ b/precompiles/types/address_test.go @@ -1,9 +1,10 @@ package types import ( - "math/big" "testing" + "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/require" @@ -52,7 +53,7 @@ func setupMockEVMAndContract(address common.Address) (vm.EVM, vm.Contract) { mockVMContract := vm.NewContract( contractRef{address: common.Address{}}, contractRef{address: common.Address{}}, - big.NewInt(0), + uint256.NewInt(0), 0, ) diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index 80d33e0cd5..06252a90c8 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -28,6 +28,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -131,7 +132,7 @@ type EVMBackend interface { PendingTransactions() ([]*sdk.Tx, error) GetCoinbase() (sdk.AccAddress, error) FeeHistory( - blockCount rpc.DecimalOrHex, + blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64, ) (*rpctypes.FeeHistoryResult, error) diff --git a/rpc/backend/chain_info.go b/rpc/backend/chain_info.go index 7ace99cf34..6945cf0e28 100644 --- a/rpc/backend/chain_info.go +++ b/rpc/backend/chain_info.go @@ -25,6 +25,7 @@ import ( tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common/hexutil" + ethmath "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -170,7 +171,7 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) { // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. func (b *Backend) FeeHistory( - userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100 + userBlockCount ethmath.HexOrDecimal64, // number blocks to fetch, maximum is 100 lastBlock rpc.BlockNumber, // the block to start search , to oldest rewardPercentiles []float64, // percentiles to fetch reward ) (*rpctypes.FeeHistoryResult, error) { diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go index b59e5c3461..da00f4dee6 100644 --- a/rpc/backend/chain_info_test.go +++ b/rpc/backend/chain_info_test.go @@ -14,6 +14,7 @@ import ( feemarkettypes "github.com/zeta-chain/ethermint/x/feemarket/types" "google.golang.org/grpc/metadata" + ethmath "github.com/ethereum/go-ethereum/common/math" "github.com/zeta-chain/node/rpc/backend/mocks" rpc "github.com/zeta-chain/node/rpc/types" ) @@ -321,7 +322,7 @@ func (suite *BackendTestSuite) TestFeeHistory() { testCases := []struct { name string registerMock func(validator sdk.AccAddress) - userBlockCount ethrpc.DecimalOrHex + userBlockCount ethmath.HexOrDecimal64 latestBlock ethrpc.BlockNumber expFeeHistory *rpc.FeeHistoryResult validator sdk.AccAddress diff --git a/rpc/backend/sign_tx.go b/rpc/backend/sign_tx.go index 105f161976..ad3175e41b 100644 --- a/rpc/backend/sign_tx.go +++ b/rpc/backend/sign_tx.go @@ -26,9 +26,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/signer/core/apitypes" + ethermint "github.com/zeta-chain/ethermint/types" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" ) @@ -70,7 +70,7 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e return common.Hash{}, err } - signer := ethtypes.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn))) + signer := ethermint.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn))) // Sign transaction if err := msg.Sign(signer, b.clientCtx.Keyring); err != nil { diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index e297053fef..5112737b36 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -31,7 +31,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" ethtypes "github.com/ethereum/go-ethereum/core/types" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" "google.golang.org/grpc/codes" @@ -138,7 +138,7 @@ func (b *Backend) processBlock( targetOneFeeHistory.BaseFee = blockBaseFee cfg := b.ChainConfig() if cfg.IsLondon(big.NewInt(blockHeight + 1)) { - targetOneFeeHistory.NextBaseFee = misc.CalcBaseFee(cfg, b.CurrentHeader()) + targetOneFeeHistory.NextBaseFee = eip1559.CalcBaseFee(cfg, b.CurrentHeader()) } else { targetOneFeeHistory.NextBaseFee = new(big.Int) } diff --git a/rpc/namespaces/ethereum/debug/api.go b/rpc/namespaces/ethereum/debug/api.go index 39288a125f..1b70bcc2f1 100644 --- a/rpc/namespaces/ethereum/debug/api.go +++ b/rpc/namespaces/ethereum/debug/api.go @@ -18,7 +18,6 @@ package debug import ( "bytes" "errors" - "fmt" "io" "os" "runtime" @@ -32,7 +31,6 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/rlp" stderrors "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" @@ -340,17 +338,6 @@ func (a *API) PrintBlock(number uint64) (string, error) { return spew.Sdump(block), nil } -// SeedHash retrieves the seed hash of a block. -func (a *API) SeedHash(number uint64) (string, error) { - // #nosec G115 number always in int64 range - _, err := a.backend.HeaderByNumber(rpctypes.BlockNumber(number)) - if err != nil { - return "", err - } - - return fmt.Sprintf("0x%x", ethash.SeedHash(number)), nil -} - // IntermediateRoots executes a block, and returns a list // of intermediate roots: the stateroot after each transaction. func (a *API) IntermediateRoots(hash common.Hash, _ *evmtypes.TraceConfig) ([]common.Hash, error) { diff --git a/rpc/namespaces/ethereum/eth/api.go b/rpc/namespaces/ethereum/eth/api.go index da8474d963..1bd68e5bed 100644 --- a/rpc/namespaces/ethereum/eth/api.go +++ b/rpc/namespaces/ethereum/eth/api.go @@ -21,6 +21,7 @@ import ( "github.com/cometbft/cometbft/libs/log" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethmath "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" ethermint "github.com/zeta-chain/ethermint/types" @@ -98,7 +99,7 @@ type EthereumAPI interface { GasPrice() (*hexutil.Big, error) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) FeeHistory( - blockCount rpc.DecimalOrHex, + blockCount ethmath.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64, ) (*rpctypes.FeeHistoryResult, error) @@ -350,7 +351,7 @@ func (e *PublicAPI) EstimateGas( return e.backend.EstimateGas(args, blockNrOptional) } -func (e *PublicAPI) FeeHistory(blockCount rpc.DecimalOrHex, +func (e *PublicAPI) FeeHistory(blockCount ethmath.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64, ) (*rpctypes.FeeHistoryResult, error) { diff --git a/server/json_rpc.go b/server/json_rpc.go index 47315892cd..9b780017b2 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -16,9 +16,11 @@ package server import ( + "context" "net/http" "time" + tmlog "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/types" @@ -27,11 +29,51 @@ import ( "github.com/gorilla/mux" "github.com/rs/cors" ethermint "github.com/zeta-chain/ethermint/types" + "golang.org/x/exp/slog" "github.com/zeta-chain/node/rpc" "github.com/zeta-chain/node/server/config" ) +type gethLogsToTm struct { + logger tmlog.Logger + attrs []slog.Attr +} + +func (g *gethLogsToTm) Enabled(_ context.Context, _ slog.Level) bool { + return true +} + +func (g *gethLogsToTm) Handle(_ context.Context, record slog.Record) error { + attrs := g.attrs + record.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + switch record.Level { + case slog.LevelDebug: + g.logger.Debug(record.Message, attrs) + case slog.LevelInfo: + g.logger.Info(record.Message, attrs) + case slog.LevelWarn: + g.logger.Info(record.Message, attrs) + case slog.LevelError: + g.logger.Error(record.Message, attrs) + } + return nil +} + +func (g *gethLogsToTm) WithAttrs(attrs []slog.Attr) slog.Handler { + return &gethLogsToTm{ + logger: g.logger, + attrs: append(g.attrs, attrs...), + } +} + +func (g *gethLogsToTm) WithGroup(_ string) slog.Handler { + return g +} + // StartJSONRPC starts the JSON-RPC server func StartJSONRPC(ctx *server.Context, clientCtx client.Context, @@ -43,17 +85,7 @@ func StartJSONRPC(ctx *server.Context, tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) logger := ctx.Logger.With("module", "geth") - ethlog.Root().SetHandler(ethlog.FuncHandler(func(r *ethlog.Record) error { - switch r.Lvl { - case ethlog.LvlTrace, ethlog.LvlDebug: - logger.Debug(r.Msg, r.Ctx...) - case ethlog.LvlInfo, ethlog.LvlWarn: - logger.Info(r.Msg, r.Ctx...) - case ethlog.LvlError, ethlog.LvlCrit: - logger.Error(r.Msg, r.Ctx...) - } - return nil - })) + ethlog.SetDefault(ethlog.NewLogger(&gethLogsToTm{logger: logger})) rpcServer := ethrpc.NewServer() diff --git a/testutil/keeper/mocks/fungible/evm.go b/testutil/keeper/mocks/fungible/evm.go index 79b2cc1985..68ebfa7443 100644 --- a/testutil/keeper/mocks/fungible/evm.go +++ b/testutil/keeper/mocks/fungible/evm.go @@ -27,7 +27,7 @@ type FungibleEVMKeeper struct { } // ApplyMessage provides a mock function with given fields: ctx, msg, tracer, commit -func (_m *FungibleEVMKeeper) ApplyMessage(ctx types.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) { +func (_m *FungibleEVMKeeper) ApplyMessage(ctx types.Context, msg *core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) { ret := _m.Called(ctx, msg, tracer, commit) if len(ret) == 0 { @@ -36,10 +36,10 @@ func (_m *FungibleEVMKeeper) ApplyMessage(ctx types.Context, msg core.Message, t var r0 *evmtypes.MsgEthereumTxResponse var r1 error - if rf, ok := ret.Get(0).(func(types.Context, core.Message, vm.EVMLogger, bool) (*evmtypes.MsgEthereumTxResponse, error)); ok { + if rf, ok := ret.Get(0).(func(types.Context, *core.Message, vm.EVMLogger, bool) (*evmtypes.MsgEthereumTxResponse, error)); ok { return rf(ctx, msg, tracer, commit) } - if rf, ok := ret.Get(0).(func(types.Context, core.Message, vm.EVMLogger, bool) *evmtypes.MsgEthereumTxResponse); ok { + if rf, ok := ret.Get(0).(func(types.Context, *core.Message, vm.EVMLogger, bool) *evmtypes.MsgEthereumTxResponse); ok { r0 = rf(ctx, msg, tracer, commit) } else { if ret.Get(0) != nil { @@ -47,7 +47,7 @@ func (_m *FungibleEVMKeeper) ApplyMessage(ctx types.Context, msg core.Message, t } } - if rf, ok := ret.Get(1).(func(types.Context, core.Message, vm.EVMLogger, bool) error); ok { + if rf, ok := ret.Get(1).(func(types.Context, *core.Message, vm.EVMLogger, bool) error); ok { r1 = rf(ctx, msg, tracer, commit) } else { r1 = ret.Error(1) diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index 144b7d8e68..7cc565936a 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -106,7 +106,7 @@ func SolanaSignature(t *testing.T) solana.Signature { // Hash returns a sample hash func Hash() ethcommon.Hash { - return EthAddress().Hash() + return ethcommon.BytesToHash(EthAddress().Bytes()) } // BtcHash returns a sample btc hash diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index c07cba213a..38726c287a 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -40,21 +40,21 @@ func (k Keeper) Hooks() Hooks { // PostTxProcessing is a wrapper for calling the EVM PostTxProcessing hook on // the module keeper -func (h Hooks) PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error { +func (h Hooks) PostTxProcessing(ctx sdk.Context, msg *core.Message, receipt *ethtypes.Receipt) error { return h.k.PostTxProcessing(ctx, msg, receipt) } // PostTxProcessing implements EvmHooks.PostTxProcessing. func (k Keeper) PostTxProcessing( ctx sdk.Context, - msg core.Message, + msg *core.Message, receipt *ethtypes.Receipt, ) error { var emittingContract ethcommon.Address - if msg.To() != nil { - emittingContract = *msg.To() + if msg.To != nil { + emittingContract = *msg.To } - return k.ProcessLogs(ctx, receipt.Logs, emittingContract, msg.From().Hex()) + return k.ProcessLogs(ctx, receipt.Logs, emittingContract, msg.From.Hex()) } // ProcessLogs post-processes logs emitted by a zEVM contract; if the log contains Withdrawal event diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 5aa70be3bd..27f5a8ad98 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -11,6 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/ethermint/x/evm/statedb" @@ -66,7 +67,7 @@ func TestKeeper_VoteInbound(t *testing.T) { err := sdkk.EvmKeeper.SetAccount(ctx, ethcommon.HexToAddress(msg.Receiver), statedb.Account{ Nonce: 0, - Balance: big.NewInt(0), + Balance: uint256.NewInt(0), CodeHash: crypto.Keccak256(nil), }) require.NoError(t, err) diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index ab0332dfe3..95b1b05dae 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" @@ -735,19 +736,19 @@ func (k Keeper) CallEVMWithData( if gasLimit != nil { gasCap = gasLimit.Uint64() } - msg := ethtypes.NewMessage( - from, - contract, - nonce, - value, // amount - gasCap, // gasLimit - big.NewInt(0), // gasFeeCap - big.NewInt(0), // gasTipCap - big.NewInt(0), // gasPrice - data, - ethtypes.AccessList{}, // AccessList - !commit, // isFake - ) + msg := &core.Message{ + From: from, + To: contract, + Nonce: nonce, + Value: value, // amount + GasLimit: gasCap, // gasLimit + GasFeeCap: big.NewInt(0), // gasFeeCap + GasTipCap: big.NewInt(0), // gasTipCap + GasPrice: big.NewInt(0), // gasPrice + Data: data, + AccessList: ethtypes.AccessList{}, // AccessList + SkipAccountChecks: !commit, // isFake + } k.evmKeeper.WithChainID(ctx) //FIXME: set chainID for signer; should not need to do this; but seems necessary. Why? k.Logger(ctx).Debug("call evm", "gasCap", gasCap, "chainid", k.evmKeeper.ChainID(), "ctx.chainid", ctx.ChainID()) res, err := k.evmKeeper.ApplyMessage(ctx, msg, evmtypes.NewNoOpTracer(), commit) @@ -799,7 +800,7 @@ func (k Keeper) CallEVMWithData( if !noEthereumTxEvent { // adding txData for more info in rpc methods in order to parse synthetic txs - attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxData, hexutil.Encode(msg.Data()))) + attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxData, hexutil.Encode(msg.Data))) // adding nonce for more info in rpc methods in order to parse synthetic txs attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxNonce, fmt.Sprint(nonce))) ctx.EventManager().EmitEvents(sdk.Events{ diff --git a/x/fungible/keeper/evm_hooks.go b/x/fungible/keeper/evm_hooks.go index 03f1ecf6cd..04bba3ccbe 100644 --- a/x/fungible/keeper/evm_hooks.go +++ b/x/fungible/keeper/evm_hooks.go @@ -22,7 +22,7 @@ func (k Keeper) EVMHooks() EVMHooks { } // PostTxProcessing is a wrapper for calling the EVM PostTxProcessing hook on the module keeper -func (h EVMHooks) PostTxProcessing(ctx sdk.Context, _ core.Message, receipt *ethtypes.Receipt) error { +func (h EVMHooks) PostTxProcessing(ctx sdk.Context, _ *core.Message, receipt *ethtypes.Receipt) error { return h.k.checkPausedZRC20(ctx, receipt) } diff --git a/x/fungible/keeper/zevm_message_passing_test.go b/x/fungible/keeper/zevm_message_passing_test.go index f8e1e300d2..f633773e8e 100644 --- a/x/fungible/keeper/zevm_message_passing_test.go +++ b/x/fungible/keeper/zevm_message_passing_test.go @@ -7,6 +7,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/ethermint/x/evm/statedb" @@ -82,7 +83,7 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { err := sdkk.EvmKeeper.SetAccount(ctx, zetaTxReceiver, statedb.Account{ Nonce: 0, - Balance: big.NewInt(0), + Balance: uint256.NewInt(0), CodeHash: crypto.Keccak256(nil), }) require.NoError(t, err) @@ -141,7 +142,7 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { err := sdkk.EvmKeeper.SetAccount(ctx, zetaTxReceiver, statedb.Account{ Nonce: 0, - Balance: big.NewInt(0), + Balance: uint256.NewInt(0), CodeHash: crypto.Keccak256(nil), }) require.NoError(t, err) @@ -231,7 +232,7 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { err := sdkk.EvmKeeper.SetAccount(ctx, zetaTxSender, statedb.Account{ Nonce: 0, - Balance: big.NewInt(0), + Balance: uint256.NewInt(0), CodeHash: crypto.Keccak256(nil), }) require.NoError(t, err) @@ -294,7 +295,7 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { err := sdkk.EvmKeeper.SetAccount(ctx, zetaTxSender, statedb.Account{ Nonce: 0, - Balance: big.NewInt(0), + Balance: uint256.NewInt(0), CodeHash: crypto.Keccak256(nil), }) require.NoError(t, err) diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index 8af00293ed..442f625c10 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -50,7 +50,7 @@ type EVMKeeper interface { EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error) ApplyMessage( ctx sdk.Context, - msg core.Message, + msg *core.Message, tracer vm.EVMLogger, commit bool, ) (*evmtypes.MsgEthereumTxResponse, error) From ad737444faeb045c61bb21a9df29f8db74145643 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 5 Nov 2024 14:50:00 -0800 Subject: [PATCH 30/34] feat: improve build reproducability (#3091) * feat: improve build reproducability * just remove the build timestamp for localnet/ci * improve naming of make targets * add changelog entry --- .github/workflows/publish-release.yml | 4 +-- .goreleaser.yaml | 9 ++++-- Makefile | 46 ++++++++++++--------------- changelog.md | 1 + 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index e6de47d1a5..a8b324de6b 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -48,9 +48,9 @@ jobs: runs-on: ${{ vars.RELEASE_RUNNER }} steps: - uses: actions/checkout@v4 - - name: Release build dry-run + - name: Build release snapshot run: | - make release-dry-run + make release-snapshot check-changelog: needs: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 37cdbcb8c2..56cab7608f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -54,11 +54,16 @@ builds: - -X github.com/cosmos/cosmos-sdk/version.ClientName=zetaclientd - -X github.com/cosmos/cosmos-sdk/version.Version={{ .Version }} - -X github.com/cosmos/cosmos-sdk/version.Commit={{ .FullCommit }} + - -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb - -X github.com/zeta-chain/node/pkg/constant.Name=zetacored - -X github.com/zeta-chain/node/pkg/constant.Version={{ .Version }} - -X github.com/zeta-chain/node/pkg/constant.CommitHash={{ .FullCommit }} - - -X github.com/zeta-chain/node/pkg/constant.BuildTime={{ .Env.BUILDTIME }} - - -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb + - -X github.com/zeta-chain/node/pkg/constant.BuildTime={{ .CommitDate }} + - -X main.version={{ .Version }} + - -X main.commit={{ .Commit }} + - -X main.date={{ .CommitDate }} + - -buildid= + - -s -w - id: "zetaclientd" main: ./cmd/zetaclientd diff --git a/Makefile b/Makefile index bcd233b12e..f39e4f27ff 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ PACKAGE_NAME := github.com/zeta-chain/node NODE_VERSION := $(shell ./version.sh) NODE_COMMIT := $(shell [ -z "${NODE_COMMIT}" ] && git log -1 --format='%H' || echo ${NODE_COMMIT} ) -BUILDTIME := $(shell date -u +"%Y%m%d.%H%M%S" ) DOCKER ?= docker # allow setting of NODE_COMPOSE_ARGS to pass additional args to docker compose # useful for setting profiles and/ort optional overlays @@ -11,19 +10,31 @@ DOCKER ?= docker DOCKER_COMPOSE ?= $(DOCKER) compose -f docker-compose.yml $(NODE_COMPOSE_ARGS) DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf GOFLAGS := "" -GOLANG_CROSS_VERSION ?= v1.22.7 GOPATH ?= '$(HOME)/go' +# common goreaser command definition +GOLANG_CROSS_VERSION ?= v1.22.7@sha256:24b2d75007f0ec8e35d01f3a8efa40c197235b200a1a91422d78b851f67ecce4 +GORELEASER := $(DOCKER) run \ + --rm \ + --privileged \ + -e CGO_ENABLED=1 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + -e "GITHUB_TOKEN=${GITHUB_TOKEN}" \ + ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} + ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=zetacore \ -X github.com/cosmos/cosmos-sdk/version.ServerName=zetacored \ -X github.com/cosmos/cosmos-sdk/version.ClientName=zetaclientd \ -X github.com/cosmos/cosmos-sdk/version.Version=$(NODE_VERSION) \ -X github.com/cosmos/cosmos-sdk/version.Commit=$(NODE_COMMIT) \ + -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb \ -X github.com/zeta-chain/node/pkg/constant.Name=zetacored \ -X github.com/zeta-chain/node/pkg/constant.Version=$(NODE_VERSION) \ -X github.com/zeta-chain/node/pkg/constant.CommitHash=$(NODE_COMMIT) \ - -X github.com/zeta-chain/node/pkg/constant.BuildTime=$(BUILDTIME) \ - -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb + -buildid= \ + -s -w BUILD_FLAGS := -ldflags '$(ldflags)' -tags pebbledb,ledger @@ -429,33 +440,18 @@ test-sim-after-import-long ### GoReleaser ### ############################################################################### -release-dry-run: - docker run \ - --rm \ - --privileged \ - -e CGO_ENABLED=1 \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v `pwd`:/go/src/$(PACKAGE_NAME) \ - -v ${GOPATH}/pkg:/go/pkg \ - -w /go/src/$(PACKAGE_NAME) \ - ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ - --clean --skip=validate --skip=publish --snapshot +release-snapshot: + $(GORELEASER) --clean --skip=validate --skip=publish --snapshot + +release-build-only: + $(GORELEASER) --clean --skip=validate --skip=publish release: @if [ ! -f ".release-env" ]; then \ echo "\033[91m.release-env is required for release\033[0m";\ exit 1;\ fi - docker run \ - --rm \ - --privileged \ - -e CGO_ENABLED=1 \ - -e "GITHUB_TOKEN=${GITHUB_TOKEN}" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v `pwd`:/go/src/$(PACKAGE_NAME) \ - -w /go/src/$(PACKAGE_NAME) \ - ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ - release --clean --skip=validate + $(GORELEASER) --clean --skip=validate ############################################################################### ### Local Mainnet Development ### diff --git a/changelog.md b/changelog.md index 3c2cd59ccb..a85e8ff86c 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Features * [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana +* [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable. ### Tests * [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert. From d2e1ffc5ed298656a1781f3e8f34071a86ed5a4f Mon Sep 17 00:00:00 2001 From: braveocheretovych Date: Wed, 6 Nov 2024 01:35:02 +0200 Subject: [PATCH 31/34] Update readme.md (#3076) Co-authored-by: Alex Gartner --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5e7230fe00..dd4b98b87f 100644 --- a/readme.md +++ b/readme.md @@ -87,6 +87,6 @@ Find below further documentation for development and running your own ZetaChain ## Community -[Twitter](https://twitter.com/zetablockchain) | +[X (formerly Twitter)](https://x.com/zetablockchain) | [Discord](https://discord.com/invite/zetachain) | [Telegram](https://t.me/zetachainofficial) | [Website](https://zetachain.com) From 234dd87cd84b40a209c21662402864d0d005a298 Mon Sep 17 00:00:00 2001 From: jkan2 Date: Tue, 5 Nov 2024 16:21:04 -0800 Subject: [PATCH 32/34] fix(ci): prevents semgrep from failing on forks (#3092) * prevents semgrep from failing when its coming from a fork * update semgrep workflow operator logic Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update .github/workflows/semgrep.yml Co-authored-by: Alex Gartner --------- Co-authored-by: jkan2 <5862123+jkan2@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Alex Gartner --- .github/workflows/semgrep.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index fcffcf7855..bcefc3da55 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -17,7 +17,9 @@ jobs: container: image: ghcr.io/zeta-chain/semgrep-semgrep:1.90.0 - if: (github.actor != 'dependabot[bot]') + if: | + github.actor != 'dependabot[bot]' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'zeta-chain/node') steps: - uses: actions/checkout@v4 - name: Checkout semgrep-utilities repo From 9cf3635c5bdf27f82b179aff8db879221fdf7506 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 5 Nov 2024 21:13:59 -0800 Subject: [PATCH 33/34] chore(e2e): increase test timeout (#3103) --- cmd/zetae2e/local/local.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index d5b4ac8076..81779faef9 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -50,7 +50,7 @@ const ( ) var ( - TestTimeout = 15 * time.Minute + TestTimeout = 20 * time.Minute ) var noError = testutil.NoError From a1fe36d41ddb0237c0524dc4b124977737af63f0 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:51:36 -0600 Subject: [PATCH 34/34] fix: replace DHT with private peer discovery (#3041) * import go-tss lib that removes DHT * replace DHT with authenticated discovery * use JSON serialization; add metric * add new telemetry: 8123/connectedpeers; fix deadlock in a few other 8123 handlers * use squashed go-tss commit * clean up interface * address review comments * remove whiteliste peers * fmt * remove rendezvous * use merged go-tss connection gater * use latest go-tss from PR#34 * use merged #34 in go-tss lib * add ping RTT to telemetry * changelog * make linter happy * pingrtt * finer resolution on pingrtt time (milliseconds => nanoseconds) * removed comments * bump go-tss to the merged commit in master branch * revert back to the go-tss commit until the PR commit is merged. --------- Co-authored-by: pwu --- changelog.md | 17 ++- cmd/zetaclientd/p2p_diagnostics.go | 230 ----------------------------- cmd/zetaclientd/start.go | 43 +++++- go.mod | 13 +- go.sum | 41 +---- zetaclient/metrics/metrics.go | 6 + zetaclient/metrics/telemetry.go | 69 ++++++++- zetaclient/tss/tss_signer.go | 1 - 8 files changed, 123 insertions(+), 297 deletions(-) delete mode 100644 cmd/zetaclientd/p2p_diagnostics.go diff --git a/changelog.md b/changelog.md index a85e8ff86c..d60a7fdfba 100644 --- a/changelog.md +++ b/changelog.md @@ -3,13 +3,20 @@ ## Unreleased ### Features - * [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana * [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable. ### Tests * [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert. +### Refactor + +### Tests + +### Fixes +* [3041](https://github.com/zeta-chain/node/pull/3041) - replace libp2p public DHT with private gossip peer discovery and connection gater for inbound connections + + ## v21.0.0 ### Features @@ -150,7 +157,7 @@ * [2518](https://github.com/zeta-chain/node/pull/2518) - add support for Solana address in zetacore * [2483](https://github.com/zeta-chain/node/pull/2483) - add priorityFee (gasTipCap) gas to the state * [2567](https://github.com/zeta-chain/node/pull/2567) - add sign latency metric to zetaclient (zetaclient_sign_latency) -* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envelop parsing +* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envelop parsing * [2560](https://github.com/zeta-chain/node/pull/2560) - add support for Solana SOL token withdraw * [2533](https://github.com/zeta-chain/node/pull/2533) - parse memo from both OP_RETURN and inscription * [2765](https://github.com/zeta-chain/node/pull/2765) - bitcoin depositor fee improvement @@ -232,7 +239,7 @@ ### CI -* [2388](https://github.com/zeta-chain/node/pull/2388) - added GitHub attestations of binaries produced in the release workflow. +* [2388](https://github.com/zeta-chain/node/pull/2388) - added GitHub attestations of binaries produced in the release workflow. * [2285](https://github.com/zeta-chain/node/pull/2285) - added nightly EVM performance testing pipeline, modified localnet testing docker image to utilize debian:bookworm, removed build-jet runners where applicable, removed deprecated/removed upgrade path testing pipeline * [2268](https://github.com/zeta-chain/node/pull/2268) - updated the publish-release pipeline to utilize the Github Actions Ubuntu 20.04 Runners * [2070](https://github.com/zeta-chain/node/pull/2070) - Added commands to build binaries from the working branch as a live full node rpc to test non-governance changes @@ -644,7 +651,7 @@ Getting the correct TSS address for Bitcoin now requires providing the Bitcoin c ### Tests -* Add unit tests for adding votes to a ballot +* Add unit tests for adding votes to a ballot ### CI @@ -684,7 +691,7 @@ Getting the correct TSS address for Bitcoin now requires providing the Bitcoin c ### Refactoring * [1226](https://github.com/zeta-chain/node/pull/1226) - call `onCrossChainCall` when depositing to a contract -* [1238](https://github.com/zeta-chain/node/pull/1238) - change default mempool version in config +* [1238](https://github.com/zeta-chain/node/pull/1238) - change default mempool version in config * [1279](https://github.com/zeta-chain/node/pull/1279) - remove duplicate funtion name IsEthereum * [1289](https://github.com/zeta-chain/node/pull/1289) - skip gas stability pool funding when gasLimit is equal gasUsed diff --git a/cmd/zetaclientd/p2p_diagnostics.go b/cmd/zetaclientd/p2p_diagnostics.go deleted file mode 100644 index b041c4993b..0000000000 --- a/cmd/zetaclientd/p2p_diagnostics.go +++ /dev/null @@ -1,230 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "sync" - "time" - - "github.com/cometbft/cometbft/crypto/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - libp2p "github.com/libp2p/go-libp2p" - dht "github.com/libp2p/go-libp2p-kad-dht" - "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing" - dutil "github.com/libp2p/go-libp2p/p2p/discovery/util" - maddr "github.com/multiformats/go-multiaddr" - "github.com/rs/zerolog" - - "github.com/zeta-chain/node/pkg/cosmos" - "github.com/zeta-chain/node/zetaclient/config" - "github.com/zeta-chain/node/zetaclient/metrics" -) - -func RunDiagnostics( - startLogger zerolog.Logger, - peers []maddr.Multiaddr, - hotkeyPk cryptotypes.PrivKey, - cfg config.Config, -) error { - startLogger.Warn().Msg("P2P Diagnostic mode enabled") - startLogger.Warn().Msgf("seed peer: %s", peers) - priKey := secp256k1.PrivKey(hotkeyPk.Bytes()[:32]) - pubkeyBech32, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, hotkeyPk.PubKey()) - if err != nil { - startLogger.Error().Err(err).Msg("Bech32ifyPubKey error") - return err - } - startLogger.Warn().Msgf("my pubkey %s", pubkeyBech32) - - var s *metrics.TelemetryServer - if len(peers) == 0 { - startLogger.Warn().Msg("No seed peer specified; assuming I'm the host") - } - p2pPriKey, err := crypto.UnmarshalSecp256k1PrivateKey(priKey[:]) - if err != nil { - startLogger.Error().Err(err).Msg("UnmarshalSecp256k1PrivateKey error") - return err - } - listenAddress, err := maddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", 6668)) - if err != nil { - startLogger.Error().Err(err).Msg("NewMultiaddr error") - return err - } - IP := os.Getenv("MYIP") - if len(IP) == 0 { - startLogger.Warn().Msg("empty env MYIP") - } - var externalAddr maddr.Multiaddr - if len(IP) != 0 { - externalAddr, err = maddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", IP, 6668)) - if err != nil { - startLogger.Error().Err(err).Msg("NewMultiaddr error") - return err - } - } - - host, err := libp2p.New( - libp2p.ListenAddrs(listenAddress), - libp2p.Identity(p2pPriKey), - libp2p.AddrsFactory(func(addrs []maddr.Multiaddr) []maddr.Multiaddr { - if externalAddr != nil { - return []maddr.Multiaddr{externalAddr} - } - return addrs - }), - libp2p.DisableRelay(), - ) - if err != nil { - startLogger.Error().Err(err).Msg("fail to create host") - return err - } - startLogger.Info().Msgf("host created: ID %s", host.ID().String()) - if len(peers) == 0 { - s = metrics.NewTelemetryServer() - s.SetP2PID(host.ID().String()) - go func() { - startLogger.Info().Msg("Starting TSS HTTP Server...") - if err := s.Start(); err != nil { - fmt.Println(err) - } - }() - } - - // create stream handler - handleStream := func(s network.Stream) { - defer s.Close() - - // read the message - buf := make([]byte, 1024) - n, err := s.Read(buf) - if err != nil { - startLogger.Error().Err(err).Msg("read stream error") - return - } - // send the message back - if _, err := s.Write(buf[:n]); err != nil { - startLogger.Error().Err(err).Msg("write stream error") - return - } - } - ProtocolID := "/echo/0.3.0" - host.SetStreamHandler(protocol.ID(ProtocolID), handleStream) - - kademliaDHT, err := dht.New(context.Background(), host, dht.Mode(dht.ModeServer)) - if err != nil { - return fmt.Errorf("fail to create DHT: %w", err) - } - startLogger.Info().Msg("Bootstrapping the DHT") - if err = kademliaDHT.Bootstrap(context.Background()); err != nil { - return fmt.Errorf("fail to bootstrap DHT: %w", err) - } - - var wg sync.WaitGroup - for _, peerAddr := range peers { - peerinfo, err := peer.AddrInfoFromP2pAddr(peerAddr) - if err != nil { - startLogger.Error().Err(err).Msgf("fail to parse peer address %s", peerAddr) - continue - } - wg.Add(1) - go func() { - defer wg.Done() - if err := host.Connect(context.Background(), *peerinfo); err != nil { - startLogger.Warn().Msgf("Connection failed with bootstrap node: %s", *peerinfo) - } else { - startLogger.Info().Msgf("Connection established with bootstrap node: %s", *peerinfo) - } - }() - } - wg.Wait() - - // We use a rendezvous point "meet me here" to announce our location. - // This is like telling your friends to meet you at the Eiffel Tower. - startLogger.Info().Msgf("Announcing ourselves...") - routingDiscovery := drouting.NewRoutingDiscovery(kademliaDHT) - dutil.Advertise(context.Background(), routingDiscovery, "ZetaZetaOpenTheDoor") - startLogger.Info().Msgf("Successfully announced!") - - // every 1min, print out the p2p diagnostic - // #nosec G115 interval is in range and not user controlled - ticker := time.NewTicker(time.Duration(cfg.P2PDiagnosticTicker) * time.Second) - round := 0 - - for range ticker.C { - round++ - // Now, look for others who have announced - // This is like your friend telling you the location to meet you. - startLogger.Info().Msgf("Searching for other peers...") - peerChan, err := routingDiscovery.FindPeers(context.Background(), "ZetaZetaOpenTheDoor") - if err != nil { - return err - } - - peerCount := 0 - okPingPongCount := 0 - for peer := range peerChan { - peerCount++ - if peer.ID == host.ID() { - startLogger.Info().Msgf("Found myself #(%d): %s", peerCount, peer) - continue - } - startLogger.Info().Msgf("Found peer #(%d): %s; pinging the peer...", peerCount, peer) - stream, err := host.NewStream(context.Background(), peer.ID, protocol.ID(ProtocolID)) - if err != nil { - startLogger.Error().Err(err).Msgf("fail to create stream to peer %s", peer) - continue - } - - // write a message to the stream - message := fmt.Sprintf( - "round %d %s => %s", - round, - host.ID().String()[len(host.ID().String())-5:], - peer.ID.String()[len(peer.ID.String())-5:], - ) - _, err = stream.Write([]byte(message)) - if err != nil { - startLogger.Error().Err(err).Msgf("fail to write to stream to peer %s", peer) - err = stream.Close() - if err != nil { - startLogger.Warn().Err(err).Msgf("fail to close stream to peer %s", peer) - } - continue - } - - // read the echoed message - buf := make([]byte, 1024) - nr, err := stream.Read(buf) - if err != nil { - startLogger.Error().Err(err).Msgf("fail to read from stream to peer %s", peer) - err = stream.Close() - if err != nil { - startLogger.Warn().Err(err).Msgf("fail to close stream to peer %s", peer) - } - continue - } - startLogger.Debug().Msgf("echoed message: %s", string(buf[:nr])) - err = stream.Close() - if err != nil { - startLogger.Warn().Err(err).Msgf("fail to close stream to peer %s", peer) - } - - // check if the message is echoed correctly - if string(buf[:nr]) != message { - startLogger.Error(). - Msgf("ping-pong failed with peer #(%d): %s; want %s got %s", peerCount, peer, message, string(buf[:nr])) - continue - } - startLogger.Info().Msgf("ping-pong success with peer #(%d): %s;", peerCount, peer) - okPingPongCount++ - } - startLogger.Info(). - Msgf("Expect %d peers in total; successful pings (%d/%d)", peerCount, okPingPongCount, peerCount-1) - } - return nil -} diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 67bd9830ee..f86a5fa2ee 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -9,11 +9,13 @@ import ( "os/signal" "path/filepath" "strings" + "sync" "syscall" "time" "github.com/cometbft/cometbft/crypto/secp256k1" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/protocol/ping" maddr "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -181,13 +183,6 @@ func start(_ *cobra.Command, _ []string) error { log.Error().Err(err).Msg("peer address error") } initPreParams(cfg.PreParamsPath) - if cfg.P2PDiagnostic { - err := RunDiagnostics(startLogger, peers, hotkeyPk, cfg) - if err != nil { - startLogger.Error().Err(err).Msg("RunDiagnostics error") - return err - } - } m, err := metrics.NewMetrics() if err != nil { @@ -235,6 +230,40 @@ func start(_ *cobra.Command, _ []string) error { signalChannel <- syscall.SIGTERM }) + go func() { + for { + time.Sleep(30 * time.Second) + ps := server.GetKnownPeers() + metrics.NumConnectedPeers.Set(float64(len(ps))) + telemetryServer.SetConnectedPeers(ps) + } + }() + go func() { + host := server.GetP2PHost() + pingRTT := make(map[peer.ID]int64) + for { + var wg sync.WaitGroup + for _, p := range whitelistedPeers { + wg.Add(1) + go func(p peer.ID) { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + result := <-ping.Ping(ctx, host, p) + if result.Error != nil { + masterLogger.Error().Err(result.Error).Msg("ping error") + pingRTT[p] = -1 // RTT -1 indicate ping error + return + } + pingRTT[p] = result.RTT.Nanoseconds() + }(p) + } + wg.Wait() + telemetryServer.SetPingRTT(pingRTT) + time.Sleep(30 * time.Second) + } + }() + // Generate a new TSS if keygen is set and add it into the tss server // If TSS has already been generated, and keygen was successful ; we use the existing TSS err = GenerateTSS(ctx, masterLogger, zetacoreClient, server) diff --git a/go.mod b/go.mod index e1b73c75c8..55b0377e8d 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,6 @@ require ( 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 @@ -188,15 +187,11 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/boxo v0.10.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect - github.com/ipfs/go-datastore v0.6.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect - github.com/ipld/go-ipld-prime v0.20.0 // indirect 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/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -210,8 +205,6 @@ require ( github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect - github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect - github.com/libp2p/go-libp2p-record v0.2.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect @@ -259,7 +252,6 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect @@ -289,7 +281,6 @@ require ( 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 github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -315,7 +306,6 @@ require ( golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/api v0.155.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect @@ -352,6 +342,7 @@ require ( github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -381,5 +372,5 @@ replace ( github.com/bnb-chain/tss-lib => github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901 github.com/ethereum/go-ethereum => github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc github.com/libp2p/go-libp2p => github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 - gitlab.com/thorchain/tss/go-tss => github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 + gitlab.com/thorchain/tss/go-tss => github.com/zeta-chain/go-tss v0.0.0-20241031223543-18765295f992 ) diff --git a/go.sum b/go.sum index 2d635ec666..6878385b68 100644 --- a/go.sum +++ b/go.sum @@ -2363,7 +2363,6 @@ github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslW github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -2640,7 +2639,6 @@ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= @@ -2854,24 +2852,16 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1 github.com/informalsystems/tm-load-test v1.0.0/go.mod h1:WVaSKaQdfZK3v0C74EMzn7//+3aeCZF8wkIKBz2/M74= github.com/informalsystems/tm-load-test v1.3.0/go.mod h1:OQ5AQ9TbT5hKWBNIwsMjn6Bf4O0U4b1kRc+0qZlQJKw= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= -github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY= -github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= -github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= -github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= -github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= -github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/httpexpect/v2 v2.3.1/go.mod h1:ICTf89VBKSD3KB0fsyyHviKF8G8hyepP0dOXJPWz3T0= @@ -2892,12 +2882,9 @@ github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3 github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= -github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= @@ -2959,7 +2946,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -3106,12 +3092,6 @@ github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFG github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= -github.com/libp2p/go-libp2p-kad-dht v0.24.2 h1:zd7myKBKCmtZBhI3I0zm8xBkb28v3gmSEtQfBdAdFwc= -github.com/libp2p/go-libp2p-kad-dht v0.24.2/go.mod h1:BShPzRbK6+fN3hk8a0WGAYKpb8m4k+DtchkqouGTrSg= -github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= -github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= -github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= -github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= @@ -3482,8 +3462,8 @@ github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkA github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -3639,8 +3619,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3/go.mod h1:q5NXNGzqj5uPnVuhGkZfmgHqNUhf15VLi6L9kW0VEc0= github.com/pointlander/jetset v1.0.1-0.20190518214125-eee7eff80bd4/go.mod h1:RdR1j20Aj5pB6+fw6Y9Ur7lMHpegTEjY1vc19hEZL40= github.com/pointlander/peg v1.0.1/go.mod h1:5hsGDQR2oZI4QoWz0/Kdg3VSVEC31iJw/b7WjqCBGRI= -github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= -github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/polyfloyd/go-errorlint v1.0.0/go.mod h1:KZy4xxPJyy88/gldCe5OdW6OQRtNO3EZE7hXzmnebgA= github.com/polyfloyd/go-errorlint v1.0.2/go.mod h1:APVvOesVSAnne5SClsPxPdfvZTVDojXh1/G3qb5wjGI= github.com/polyfloyd/go-errorlint v1.0.5/go.mod h1:APVvOesVSAnne5SClsPxPdfvZTVDojXh1/G3qb5wjGI= @@ -3874,12 +3852,10 @@ github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXi github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs= github.com/smartystreets/assertions v1.13.0/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= @@ -4122,9 +4098,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= -github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= @@ -4159,10 +4134,6 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= @@ -4216,8 +4187,8 @@ github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc h1:FVOt github.com/zeta-chain/go-ethereum v1.13.16-0.20241022183758-422c6ef93ccc/go.mod h1:MgO2/CmxFnj6W7v/5hrz3ypco3kHkb8856pRnFkY4xQ= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 h1:FmO3HfVdZ7LzxBUfg6sVzV7ilKElQU2DZm8PxJ7KcYI= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4/go.mod h1:TBv5NY/CqWYIfUstXO1fDWrt4bDoqgCw79yihqBspg8= -github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949 h1:dBwx99+oymiyecnRGu1dnkJmYn2SAgBexBJ6nsdJt+E= -github.com/zeta-chain/go-tss v0.0.0-20241028203048-62ae2bb54949/go.mod h1:B1FDE6kHs8hozKSX1/iXgCdvlFbS6+FeAupoBHDK0Cc= +github.com/zeta-chain/go-tss v0.0.0-20241031223543-18765295f992 h1:jpfOoQGHQo29CKZaAPLCjguj35ikpV4UHFI99nL3fVA= +github.com/zeta-chain/go-tss v0.0.0-20241031223543-18765295f992/go.mod h1:nqelgf4HKkqlXaVg8X38a61WfyYB+ivCt6nnjoTIgCc= github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 h1:vck/FcIIpFOvpBUm0NO17jbEtmSz/W/a5Y4jRuSJl6I= github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138/go.mod h1:U494OsZTWsU75hqoriZgMdSsgSGP1mUL1jX+wN/Aez8= github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c h1:ZoFxMMZtivRLquXVq1sEVlT45UnTPMO1MSXtc88nDv4= @@ -5191,8 +5162,6 @@ gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index a0a7341f94..a826da58f2 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -148,6 +148,12 @@ var ( }, []string{"host"}, ) + + NumConnectedPeers = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "num_connected_peers", + Help: "The number of connected peers (authenticated keygen peers)", + }) ) // NewMetrics creates a new Metrics instance diff --git a/zetaclient/metrics/telemetry.go b/zetaclient/metrics/telemetry.go index d0cd85b538..506945859c 100644 --- a/zetaclient/metrics/telemetry.go +++ b/zetaclient/metrics/telemetry.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gorilla/mux" + "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -30,6 +31,8 @@ type TelemetryServer struct { status types.Status ipAddress string HotKeyBurnRate *BurnRate + connectedPeers []peer.AddrInfo + rtt map[peer.ID]int64 } // NewTelemetryServer should only listen to the loopback @@ -39,6 +42,8 @@ func NewTelemetryServer() *TelemetryServer { lastScannedBlockNumber: make(map[int64]uint64), lastStartTimestamp: time.Now(), HotKeyBurnRate: NewBurnRate(100), + connectedPeers: make([]peer.AddrInfo, 0), + rtt: make(map[peer.ID]int64), } s := &http.Server{ Addr: ":8123", @@ -50,6 +55,30 @@ func NewTelemetryServer() *TelemetryServer { return hs } +func (t *TelemetryServer) SetPingRTT(rtt map[peer.ID]int64) { + t.mu.Lock() + defer t.mu.Unlock() + t.rtt = rtt +} + +func (t *TelemetryServer) GetPingRTT() map[peer.ID]int64 { + t.mu.Lock() + defer t.mu.Unlock() + return t.rtt +} + +func (t *TelemetryServer) SetConnectedPeers(peers []peer.AddrInfo) { + t.mu.Lock() + defer t.mu.Unlock() + t.connectedPeers = peers +} + +func (t *TelemetryServer) GetConnectedPeers() []peer.AddrInfo { + t.mu.Lock() + defer t.mu.Unlock() + return t.connectedPeers +} + // SetP2PID sets p2pid func (t *TelemetryServer) SetP2PID(p2pid string) { t.mu.Lock() @@ -145,7 +174,8 @@ func (t *TelemetryServer) Handlers() http.Handler { router.Handle("/status", http.HandlerFunc(t.statusHandler)).Methods(http.MethodGet) router.Handle("/ip", http.HandlerFunc(t.ipHandler)).Methods(http.MethodGet) router.Handle("/hotkeyburnrate", http.HandlerFunc(t.hotKeyFeeBurnRate)).Methods(http.MethodGet) - + router.Handle("/connectedpeers", http.HandlerFunc(t.connectedPeersHandler)).Methods(http.MethodGet) + router.Handle("/pingrtt", http.HandlerFunc(t.pingRTTHandler)).Methods(http.MethodGet) router.Use(logMiddleware()) return router @@ -184,17 +214,14 @@ func (t *TelemetryServer) pingHandler(w http.ResponseWriter, _ *http.Request) { // p2pHandler returns the p2p id func (t *TelemetryServer) p2pHandler(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - t.mu.Lock() - defer t.mu.Unlock() - fmt.Fprintf(w, "%s", t.p2pid) + fmt.Fprintf(w, "%s", t.GetP2PID()) } // ipHandler returns the ip address func (t *TelemetryServer) ipHandler(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - t.mu.Lock() - defer t.mu.Unlock() - fmt.Fprintf(w, "%s", t.ipAddress) + + fmt.Fprintf(w, "%s", t.GetIPAddress()) } func (t *TelemetryServer) lastScannedBlockHandler(w http.ResponseWriter, _ *http.Request) { @@ -251,6 +278,34 @@ func (t *TelemetryServer) hotKeyFeeBurnRate(w http.ResponseWriter, _ *http.Reque fmt.Fprintf(w, "%v", t.HotKeyBurnRate.GetBurnRate()) } +func (t *TelemetryServer) connectedPeersHandler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + peers := t.GetConnectedPeers() + data, err := json.Marshal(peers) + if err != nil { + t.logger.Error().Err(err).Msg("Failed to marshal connected peers") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "%s", string(data)) +} + +func (t *TelemetryServer) pingRTTHandler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + rtt := t.GetPingRTT() + rtt2 := make(map[string]int64) + for k, v := range rtt { + rtt2[k.String()] = v + } + data, err := json.Marshal(rtt2) + if err != nil { + t.logger.Error().Err(err).Msg("Failed to marshal ping RTT") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "%s", string(data)) +} + // logMiddleware logs the incoming HTTP request func logMiddleware() mux.MiddlewareFunc { return func(handler http.Handler) http.Handler { diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index 594784797c..e3df81d7ad 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -174,7 +174,6 @@ func SetupTSSServer( bootstrapPeers, 6668, privkey, - "MetaMetaOpenTheDoor", tsspath, thorcommon.TssConfig{ EnableMonitor: enableMonitor,