diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5a69e28d0..a8589f16a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -4,7 +4,12 @@ on: push: branches: - main + - native-reward # temp pull_request: + branches: + - main + - native-reward # temp + jobs: build: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index cb48eee09..9aad103e9 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -2,9 +2,13 @@ name: Integration tests on: push: - branches: [master, main] + branches: + - main + - native-reward # temp pull_request: - branches: [master, main] + branches: + - main + - native-reward # temp jobs: happy-path-hardhat: diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 1d4563703..99e16e978 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,9 +5,13 @@ name: Solidity contract build and test on: push: - branches: [master, main] + branches: + - main + - native-reward # temp pull_request: - branches: [master, main] + branches: + - main + - native-reward # temp jobs: core-tests: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 98af9df22..ca9845b3d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,7 +4,11 @@ on: push: branches: - main + - native-reward # temp pull_request: + branches: + - main + - native-reward # temp env: CARGO_TERM_COLOR: always diff --git a/module/proto/gravity/v1/msgs.proto b/module/proto/gravity/v1/msgs.proto index 5a07bb5b1..31972675c 100644 --- a/module/proto/gravity/v1/msgs.proto +++ b/module/proto/gravity/v1/msgs.proto @@ -246,8 +246,9 @@ message MsgValsetUpdatedClaim { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; - string reward_token = 6; - string orchestrator = 7; + string reward_denom = 6; + string reward_recipient = 7; + string orchestrator = 8; } message MsgValsetUpdatedClaimResponse {} diff --git a/module/proto/gravity/v1/types.proto b/module/proto/gravity/v1/types.proto index 46a8b1b93..28cbd15ef 100644 --- a/module/proto/gravity/v1/types.proto +++ b/module/proto/gravity/v1/types.proto @@ -22,8 +22,8 @@ message Valset { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; - // the reward token in it's Ethereum hex address representation - string reward_token = 5; + // the cosmos native reward token/denom + string reward_denom = 5; } diff --git a/module/x/gravity/abci_test.go b/module/x/gravity/abci_test.go index a405ecb60..eb9f78510 100644 --- a/module/x/gravity/abci_test.go +++ b/module/x/gravity/abci_test.go @@ -8,11 +8,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/keeper" - "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/types" sdk "github.com/cosmos/cosmos-sdk/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/keeper" + "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/types" ) func TestValsetCreationIfNotAvailable(t *testing.T) { @@ -21,7 +22,7 @@ func TestValsetCreationIfNotAvailable(t *testing.T) { // EndBlocker should set a new validator set if not available EndBlocker(ctx, pk) - require.NotNil(t, pk.GetValset(ctx, uint64(pk.GetLatestValsetNonce(ctx)))) + require.NotNil(t, pk.GetValset(ctx, pk.GetLatestValsetNonce(ctx))) valsets := pk.GetValsets(ctx) require.True(t, len(valsets) == 1) } diff --git a/module/x/gravity/handler_test.go b/module/x/gravity/handler_test.go index cd4124c6d..c4d02ae5a 100644 --- a/module/x/gravity/handler_test.go +++ b/module/x/gravity/handler_test.go @@ -656,7 +656,7 @@ func TestMsgValsetConfirm(t *testing.T) { blockHeight int64 = 200 signature = "7c331bd8f2f586b04a2e2cafc6542442ef52e8b8be49533fa6b8962e822bc01e295a62733abfd65a412a8de8286f2794134c160c27a2827bdb71044b94b003cc1c" badSignature = "6c331bd8f2f586b04a2e2cafc6542442ef52e8b8be49533fa6b8962e822bc01e295a62733abfd65a412a8de8286f2794134c160c27a2827bdb71044b94b003cc1c" - ethAddress = "0xd62FF457C6165FF214C1658c993A8a203E601B03" + ethAddress = "0x6DBd7922e7f9502191ECe180635942f2DcEa73BC" wrongAddress = "0xb9a2c7853F181C3dd4a0517FCb9470C0f709C08C" ) ethAddressParsed, err := types.NewEthAddress(ethAddress) diff --git a/module/x/gravity/keeper/attestation.go b/module/x/gravity/keeper/attestation.go index d3fd4630d..869859284 100644 --- a/module/x/gravity/keeper/attestation.go +++ b/module/x/gravity/keeper/attestation.go @@ -339,7 +339,7 @@ func (k Keeper) GetLastObservedValset(ctx sdk.Context) *types.Valset { Members: []types.BridgeValidator{}, Height: 0, RewardAmount: sdk.Int{}, - RewardToken: "", + RewardDenom: "", } k.cdc.MustUnmarshal(bytes, &valset) return &valset diff --git a/module/x/gravity/keeper/attestation_handler.go b/module/x/gravity/keeper/attestation_handler.go index 0a653f0d7..de40e1501 100644 --- a/module/x/gravity/keeper/attestation_handler.go +++ b/module/x/gravity/keeper/attestation_handler.go @@ -2,15 +2,15 @@ package keeper import ( "fmt" - distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" "math/big" "strconv" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" distypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/types" ) @@ -271,64 +271,13 @@ func (a AttestationHandler) Handle(ctx sdk.Context, att types.Attestation, claim ), ) case *types.MsgValsetUpdatedClaim: - rewardAddress, err := types.NewEthAddress(claim.RewardToken) - if err != nil { - return sdkerrors.Wrap(err, "invalid reward token on claim") - } - // TODO here we should check the contents of the validator set against - // the store, if they differ we should take some action to indicate to the - // user that bridge highjacking has occurred a.keeper.SetLastObservedValset(ctx, types.Valset{ Nonce: claim.ValsetNonce, Members: claim.Members, Height: 0, RewardAmount: claim.RewardAmount, - RewardToken: claim.RewardToken, + RewardDenom: claim.RewardDenom, }) - // if the reward is greater than zero and the reward token - // is valid then some reward was issued by this validator set - // and we need to either add to the total tokens for a Cosmos native - // token, or burn non cosmos native tokens - if claim.RewardAmount.GT(sdk.ZeroInt()) && claim.RewardToken != types.ZeroAddressString { - // Check if coin is Cosmos-originated asset and get denom - isCosmosOriginated, denom := a.keeper.ERC20ToDenomLookup(ctx, *rewardAddress) - if isCosmosOriginated { - // If it is cosmos originated, mint some coins to account - // for coins that now exist on Ethereum and may eventually come - // back to Cosmos. - // - // Note the flow is - // user relays valset and gets reward -> event relayed to cosmos mints tokens to module - // -> user sends tokens to cosmos and gets the minted tokens from the module - // - // it is not possible for this to be a race condition thanks to the event nonces - // no matter how long it takes to relay the valset updated event the deposit event - // for the user will always come after. - // - // Note we are minting based on the claim! This is important as the reward value - // could change between when this event occurred and the present - coins := sdk.Coins{sdk.NewCoin(denom, claim.RewardAmount)} - if err := a.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute("MsgValsetUpdatedClaim", strconv.Itoa(int(claim.GetEventNonce()))), - ), - ) - return sdkerrors.Wrapf(err, "unable to mint cosmos originated coins %v", coins) - } - } else { - // // If it is not cosmos originated, burn the coins (aka Vouchers) - // // so that we don't think we have more in the bridge than we actually do - // coins := sdk.Coins{sdk.NewCoin(denom, claim.RewardAmount)} - // a.bankKeeper.BurnCoins(ctx, types.ModuleName, coins) - - // if you want to issue Ethereum originated tokens remove this panic and uncomment - // the above code but note that you will have to constantly replenish the tokens in the - // module or your chain will eventually halt. - panic("Can not use Ethereum originated token as reward!") - } - } ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, @@ -336,6 +285,41 @@ func (a AttestationHandler) Handle(ctx sdk.Context, att types.Attestation, claim ), ) + // if the reward is greater than zero and the reward token isn't nil we process the reward + if !claim.RewardAmount.IsNil() && claim.RewardAmount.GT(sdk.ZeroInt()) && claim.RewardDenom != "" { + // Mint some coins to account and send to the recipient. + // + // Note the flow is + // user relays valset -> event relayed to cosmos mints tokens to module and sends to recipient + // + // it is not possible for this to be a race condition thanks to the event nonces + // no matter how long it takes to relay the valset updated event the deposit event + // for the user will always come after. + // + // Note we are minting based on the claim! This is important as the reward value + // could change between when this event occurred and the present + coins := sdk.Coins{sdk.NewCoin(claim.RewardDenom, claim.RewardAmount)} + if err := a.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { + return sdkerrors.Wrapf(err, "unable to mint cosmos originated coins for valset reward, coins: %v", + coins) + } + + // If the recipient address is valid we send the minted coins to the provided address. + // If the address is wrong, the minted coins will be sent to the community pool. + recipient, err := sdk.AccAddressFromBech32(claim.RewardRecipient) + if err != nil { + if err := a.SendToCommunityPool(ctx, coins); err != nil { + return sdkerrors.Wrapf(err, "unable to send coins to community pool for valset reward, coins: %v", + coins) + } + return nil + } + if err := a.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil { + return sdkerrors.Wrapf(err, "unable to send coins to recipient for valset reward, coins: %v, %s", + coins, recipient.String()) + } + } + default: panic(fmt.Sprintf("Invalid event type for attestations %s", claim.GetType())) } diff --git a/module/x/gravity/keeper/attestation_test.go b/module/x/gravity/keeper/attestation_test.go index 98b57ead4..8eccea41d 100644 --- a/module/x/gravity/keeper/attestation_test.go +++ b/module/x/gravity/keeper/attestation_test.go @@ -3,10 +3,13 @@ package keeper import ( "testing" - "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdktypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + distypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/stretchr/testify/require" + + "github.com/onomyprotocol/cosmos-gravity-bridge/module/x/gravity/types" ) // Sets up 10 attestations and checks that they are returned in the correct order @@ -24,7 +27,7 @@ func TestGetMostRecentAttestations(t *testing.T) { EventNonce: nonce, BlockHeight: 1, TokenContract: "0x00000000000000000001", - Amount: sdktypes.NewInt(10000000000 + int64(i)), + Amount: sdk.NewInt(10000000000 + int64(i)), EthereumSender: "0x00000000000000000002", CosmosReceiver: "0x00000000000000000003", Orchestrator: "0x00000000000000000004", @@ -51,3 +54,50 @@ func TestGetMostRecentAttestations(t *testing.T) { "The %vth claim does not match our message: claim %v\n message %v", n, attest.Claim, msgs[n]) } } + +func TestHandleMsgValsetUpdatedClaim(t *testing.T) { + rewardAmount := sdk.NewInt(100) + + rewardRecipient := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + + testEnv := CreateTestEnv(t) + + accountKeeper := testEnv.AccountKeeper + bankKeeper := testEnv.BankKeeper + gravityKeeper := testEnv.GravityKeeper + stakingKeeper := testEnv.StakingKeeper + + ctx := testEnv.Context + + rewardDenom := stakingKeeper.BondDenom(ctx) + distAccount := accountKeeper.GetModuleAddress(distypes.ModuleName) + initialDistBalanceAmount := bankKeeper.GetBalance(ctx, distAccount, rewardDenom).Amount + + // empty message + msg := &types.MsgValsetUpdatedClaim{} + err := gravityKeeper.AttestationHandler.Handle(ctx, types.Attestation{}, msg) + require.NoError(t, err) + + // with valid reward and recipient + msg = &types.MsgValsetUpdatedClaim{ + RewardAmount: rewardAmount, + RewardDenom: rewardDenom, + RewardRecipient: rewardRecipient.String(), + } + err = gravityKeeper.AttestationHandler.Handle(ctx, types.Attestation{}, msg) + require.NoError(t, err) + recipientBalanceAmount := bankKeeper.GetBalance(ctx, rewardRecipient, rewardDenom).Amount + require.Equal(t, rewardAmount, recipientBalanceAmount) + + // with valid reward and invalid recipient (goes to community pool) + msg = &types.MsgValsetUpdatedClaim{ + RewardAmount: rewardAmount, + RewardDenom: rewardDenom, + RewardRecipient: "invalid-recipient-address", + } + err = gravityKeeper.AttestationHandler.Handle(ctx, types.Attestation{}, msg) + require.NoError(t, err) + + distBalanceAmount := bankKeeper.GetBalance(ctx, distAccount, rewardDenom).Amount + require.Equal(t, rewardAmount, distBalanceAmount.Sub(initialDistBalanceAmount)) +} diff --git a/module/x/gravity/keeper/cosmos-originated.go b/module/x/gravity/keeper/cosmos-originated.go index 1061678a5..ab3138a96 100644 --- a/module/x/gravity/keeper/cosmos-originated.go +++ b/module/x/gravity/keeper/cosmos-originated.go @@ -64,27 +64,6 @@ func (k Keeper) DenomToERC20Lookup(ctx sdk.Context, denom string) (bool, *types. return false, tc1, nil } -// RewardToERC20Lookup is a specialized function wrapping DenomToERC20Lookup designed to validate -// the validator set reward any time we generate a validator set -func (k Keeper) RewardToERC20Lookup(ctx sdk.Context, coin sdk.Coin) (*types.EthAddress, sdk.Int) { - if !coin.IsValid() || coin.IsZero() { - panic("Bad validator set relaying reward!") - } else { - // reward case, pass to DenomToERC20Lookup - _, address, err := k.DenomToERC20Lookup(ctx, coin.Denom) - if err != nil { - // This can only ever happen if governance sets a value for the reward - // which is not a valid ERC20 that as been bridged before (either from or to Cosmos) - // We'll classify that as operator error and just panic - panic("Invalid Valset reward! Correct or remove the paramater value") - } - if err != nil { - panic("Invalid Valset reward! Correct or remove the paramater value") - } - return address, coin.Amount - } -} - // ERC20ToDenom returns (bool isCosmosOriginated, string denom, err) // Using this information, you can see if an ERC20 address representing an asset is native to Cosmos or Ethereum, // and get its corresponding denom diff --git a/module/x/gravity/keeper/genesis.go b/module/x/gravity/keeper/genesis.go index a5dea8375..b248024ad 100644 --- a/module/x/gravity/keeper/genesis.go +++ b/module/x/gravity/keeper/genesis.go @@ -158,14 +158,13 @@ func InitGenesis(ctx sdk.Context, k Keeper, data types.GenesisState) { k.setCosmosOriginatedDenomToERC20(ctx, item.Denom, *ethAddr) } - // now that we have the denom-erc20 mapping we need to validate - // that the valset reward is possible and cosmos originated remove - // this if you want a non-cosmos originated reward + // the valset reward is possible in cosmos native tokens only valsetReward := k.GetParams(ctx).ValsetReward if valsetReward.IsValid() && !valsetReward.IsZero() { - _, exists := k.GetCosmosOriginatedERC20(ctx, valsetReward.Denom) + _, exists := k.bankKeeper.GetDenomMetaData(ctx, valsetReward.Denom) if !exists { - panic("Invalid Cosmos originated denom for valset reward") + panic(fmt.Sprintf("Invalid Cosmos originated denom for valset reward, denom %s "+ + "not found in the bank keeper metadata", valsetReward.Denom)) } } diff --git a/module/x/gravity/keeper/keeper_valset.go b/module/x/gravity/keeper/keeper_valset.go index 881d4f40a..69a8e2e8e 100644 --- a/module/x/gravity/keeper/keeper_valset.go +++ b/module/x/gravity/keeper/keeper_valset.go @@ -5,7 +5,6 @@ import ( "math/big" "sort" "strconv" - "strings" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -265,14 +264,9 @@ func (k Keeper) GetCurrentValset(ctx sdk.Context) (types.Valset, error) { // on how many validators have keys set. bridgeValidators := make([]*types.InternalBridgeValidator, 0, len(validators)) totalPower := sdk.NewInt(0) - // TODO someone with in depth info on Cosmos staking should determine - // if this is doing what I think it's doing + for _, validator := range validators { val := validator.GetOperator() - if err := sdk.VerifyAddressFormat(val); err != nil { - return types.Valset{}, sdkerrors.Wrap(err, types.ErrInvalidValAddress.Error()) - } - p := sdk.NewInt(k.StakingKeeper.GetLastValidatorPower(ctx, val)) if ethAddr, found := k.GetEthAddressByValidator(ctx, val); found { @@ -290,36 +284,22 @@ func (k Keeper) GetCurrentValset(ctx sdk.Context) (types.Valset, error) { bridgeValidators[i].Power = normalizeValidatorPower(bridgeValidators[i].Power, totalPower) } - // get the reward from the params store - reward := k.GetParams(ctx).ValsetReward - var rewardToken *types.EthAddress - var rewardAmount sdk.Int - if !reward.IsValid() || reward.IsZero() { - // the case where a validator has 'no reward'. The 'no reward' value is interpreted as having a zero - // address for the ERC20 token and a zero value for the reward amount. Since we store a coin with the - // params, a coin with a blank denom and/or zero amount is interpreted in this way. - za := types.ZeroAddress() - rewardToken = &za - rewardAmount = sdk.NewIntFromUint64(0) - - } else { - rewardToken, rewardAmount = k.RewardToERC20Lookup(ctx, reward) + rewardDenom := "" + rewardAmount := sdk.ZeroInt() + rewardParams := k.GetParams(ctx).ValsetReward + if rewardParams.IsValid() && !rewardParams.IsZero() { + rewardDenom = rewardParams.Denom + rewardAmount = rewardParams.Amount } // increment the nonce, since this potential future valset should be after the current valset valsetNonce := k.GetLatestValsetNonce(ctx) + 1 - valset, err := types.NewValset(valsetNonce, uint64(ctx.BlockHeight()), bridgeValidators, rewardAmount, *rewardToken) + valset, err := types.NewValset(valsetNonce, uint64(ctx.BlockHeight()), bridgeValidators, rewardAmount, rewardDenom) if err != nil { return types.Valset{}, (sdkerrors.Wrap(err, types.ErrInvalidValset.Error())) } - // this part is important for gravity contract - // since we expect the valset to be sorted by Eth Address in ASC order - sort.Slice(valset.Members, func(i, j int) bool { - return strings.ToLower(valset.Members[i].EthereumAddress) < strings.ToLower(valset.Members[j].EthereumAddress) - }) - return *valset, nil } diff --git a/module/x/gravity/keeper/msg_server.go b/module/x/gravity/keeper/msg_server.go index 8b5ec1e52..5e08de84c 100644 --- a/module/x/gravity/keeper/msg_server.go +++ b/module/x/gravity/keeper/msg_server.go @@ -95,7 +95,7 @@ func (k msgServer) ValsetConfirm(c context.Context, msg *types.MsgValsetConfirm) } err = k.confirmHandlerCommon(ctx, msg.EthAddress, msg.Orchestrator, msg.Signature, checkpoint) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, fmt.Sprintf("for valset confirm, msg: %+v", msg)) } // persist signature @@ -201,7 +201,7 @@ func (k msgServer) ConfirmBatch(c context.Context, msg *types.MsgConfirmBatch) ( orchaddr, _ := sdk.AccAddressFromBech32(msg.Orchestrator) err = k.confirmHandlerCommon(ctx, msg.EthSigner, msg.Orchestrator, msg.Signature, checkpoint) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, "for batch confirm") } // check if we already have this confirm @@ -243,7 +243,7 @@ func (k msgServer) ConfirmLogicCall(c context.Context, msg *types.MsgConfirmLogi } err = k.confirmHandlerCommon(ctx, msg.EthSigner, msg.Orchestrator, msg.Signature, checkpoint) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, "for logic call confirm") } // check if we already have this confirm @@ -302,8 +302,7 @@ func (k msgServer) claimHandlerCommon(ctx sdk.Context, msgAny *codectypes.Any, m sdk.NewEvent( sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, string(msg.GetType())), - // TODO: maybe return something better here? is this the right string representation? - sdk.NewAttribute(types.AttributeKeyAttestationID, string(types.GetAttestationKey(msg.GetEventNonce(), hash))), + sdk.NewAttribute(types.AttributeKeyAttestationID, types.GetAttestationKey(msg.GetEventNonce(), hash)), ), ) @@ -352,7 +351,6 @@ func (k msgServer) confirmHandlerCommon(ctx sdk.Context, ethAddress string, orch } // DepositClaim handles MsgSendToCosmosClaim -// TODO it is possible to submit an old msgDepositClaim (old defined as covering an event nonce that has already been // executed aka 'observed' and had it's slashing window expire) that will never be cleaned up in the endblocker. This // should not be a security risk as 'old' events can never execute but it does store spam in the chain. func (k msgServer) SendToCosmosClaim(c context.Context, msg *types.MsgSendToCosmosClaim) (*types.MsgSendToCosmosClaimResponse, error) { @@ -375,7 +373,6 @@ func (k msgServer) SendToCosmosClaim(c context.Context, msg *types.MsgSendToCosm } // WithdrawClaim handles MsgBatchSendToEthClaim -// TODO it is possible to submit an old msgWithdrawClaim (old defined as covering an event nonce that has already been // executed aka 'observed' and had it's slashing window expire) that will never be cleaned up in the endblocker. This // should not be a security risk as 'old' events can never execute but it does store spam in the chain. func (k msgServer) BatchSendToEthClaim(c context.Context, msg *types.MsgBatchSendToEthClaim) (*types.MsgBatchSendToEthClaimResponse, error) { diff --git a/module/x/gravity/keeper/querier_test.go b/module/x/gravity/keeper/querier_test.go index 74cc2ae3a..5c49ed78f 100644 --- a/module/x/gravity/keeper/querier_test.go +++ b/module/x/gravity/keeper/querier_test.go @@ -147,14 +147,13 @@ func TestAllValsetConfirmsBynonce(t *testing.T) { } } -// TODO: Check failure modes //nolint: exhaustivestruct func TestLastValsetRequests(t *testing.T) { val1 := types.Valset{ Nonce: 6, Height: 1235167, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 858993459, @@ -183,7 +182,7 @@ func TestLastValsetRequests(t *testing.T) { Nonce: 5, Height: 1235067, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 858993459, @@ -212,7 +211,7 @@ func TestLastValsetRequests(t *testing.T) { Nonce: 4, Height: 1234967, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 1073741824, @@ -237,7 +236,7 @@ func TestLastValsetRequests(t *testing.T) { Nonce: 3, Height: 1234867, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 1431655765, @@ -258,7 +257,7 @@ func TestLastValsetRequests(t *testing.T) { Nonce: 2, Height: 1234767, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 2147483648, @@ -306,7 +305,6 @@ func TestLastValsetRequests(t *testing.T) { } //nolint: exhaustivestruct -// TODO: check that it doesn't accidently return a valset that HAS been signed // Right now it is basically just testing that any valset comes back func TestPendingValsetRequests(t *testing.T) { specs := map[string]struct { @@ -318,7 +316,7 @@ func TestPendingValsetRequests(t *testing.T) { Nonce: 6, Height: 1235167, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 858993459, @@ -346,7 +344,7 @@ func TestPendingValsetRequests(t *testing.T) { Nonce: 5, Height: 1235067, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 858993459, @@ -374,7 +372,7 @@ func TestPendingValsetRequests(t *testing.T) { Nonce: 4, Height: 1234967, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 1073741824, @@ -398,7 +396,7 @@ func TestPendingValsetRequests(t *testing.T) { Nonce: 3, Height: 1234867, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 1431655765, @@ -418,7 +416,7 @@ func TestPendingValsetRequests(t *testing.T) { Nonce: 2, Height: 1234767, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 2147483648, @@ -440,7 +438,7 @@ func TestPendingValsetRequests(t *testing.T) { }, Height: 1234667, RewardAmount: sdk.NewInt(0), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", }, }, }, @@ -474,7 +472,6 @@ func TestPendingValsetRequests(t *testing.T) { } //nolint: exhaustivestruct -// TODO: check that it actually returns a batch that has NOT been signed, not just any batch func TestLastPendingBatchRequest(t *testing.T) { specs := map[string]struct { @@ -646,7 +643,6 @@ func TestQueryLogicCalls(t *testing.T) { var validators []sdk.ValAddress for j := 0; j <= i; j++ { // add an validator each block - // TODO: replace with real SDK addresses valAddr := bytes.Repeat([]byte{byte(j)}, 20) ethAddr, err := types.NewEthAddress(gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(j + 1)}, 20)).String()) require.NoError(t, err) @@ -704,7 +700,6 @@ func TestQueryLogicCallsConfirms(t *testing.T) { var validators []sdk.ValAddress for j := 0; j <= i; j++ { // add an validator each block - // TODO: replace with real SDK addresses valAddr := bytes.Repeat([]byte{byte(j)}, 20) ethAddr, err := types.NewEthAddress(gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(j + 1)}, 20)).String()) require.NoError(t, err) @@ -746,7 +741,6 @@ func TestQueryLogicCallsConfirms(t *testing.T) { } //nolint: exhaustivestruct -// TODO: test that it gets the correct batch, not just any batch. // Check with multiple nonces and tokenContracts func TestQueryBatch(t *testing.T) { input := CreateTestEnv(t) @@ -799,7 +793,6 @@ func TestQueryBatch(t *testing.T) { }, } - // TODO: this test is failing on the empty representation of valset members assert.Equal(t, &expectedRes, batch, batch) } @@ -912,7 +905,7 @@ func TestQueryCurrentValset(t *testing.T) { Nonce: 1, Height: 1234567, RewardAmount: sdk.ZeroInt(), - RewardToken: "0x0000000000000000000000000000000000000000", + RewardDenom: "", Members: []types.BridgeValidator{ { Power: 858993459, diff --git a/module/x/gravity/types/abi_json.go b/module/x/gravity/types/abi_json.go index 94dfaaa1e..5b89db941 100644 --- a/module/x/gravity/types/abi_json.go +++ b/module/x/gravity/types/abi_json.go @@ -1,5 +1,12 @@ package types +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + // The go-ethereum ABI encoder *only* encodes function calls and then it only encodes // function calls for which you provide an ABI json just like you would get out of the // solidity compiler with your compiled contract. @@ -13,9 +20,15 @@ package types // 'function specification' that will encode the same arguments into a function call. We can then // truncate the first several bytes where the call name is encoded to finally get the equal of the -const ( +var ( + OutgoingBatchTxCheckpointABI, ValsetCheckpointABI, OutgoingLogicCallABI abi.ABI +) + +func init() { + var err error + // OutgoingBatchTxCheckpointABIJSON checks the ETH ABI for compatability of the OutgoingBatchTx message - OutgoingBatchTxCheckpointABIJSON = `[{ + OutgoingBatchTxCheckpointABI, err = abi.JSON(strings.NewReader(`[{ "name": "submitBatch", "stateMutability": "pure", "type": "function", @@ -32,10 +45,14 @@ const ( "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ] - }]` + }]`)) + + if err != nil { + panic(fmt.Sprintf("can't decode OutgoingBatchTxCheckpointABI, %s", err)) + } // ValsetCheckpointABIJSON checks the ETH ABI for compatability of the Valset update message - ValsetCheckpointABIJSON = `[{ + ValsetCheckpointABI, err = abi.JSON(strings.NewReader(`[{ "name": "checkpoint", "stateMutability": "pure", "type": "function", @@ -45,16 +62,20 @@ const ( { "internalType": "uint256", "name": "_valsetNonce", "type": "uint256" }, { "internalType": "address[]", "name": "_validators", "type": "address[]" }, { "internalType": "uint256[]", "name": "_powers", "type": "uint256[]" }, - { "internalType": "uint256", "name": "_rewardAmount","type": "uint256" }, - { "internalType": "address", "name": "_rewardToken", "type": "address" } + { "internalType": "uint256", "name": "_rewardAmount", "type": "uint256" }, + { "internalType": "string", "name": "_rewardDenom", "type": "string" } ], "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ] - }]` + }]`)) + + if err != nil { + panic(fmt.Sprintf("can't decode ValsetCheckpointABI, %s", err)) + } // OutgoingLogicCallABIJSON checks the ETH ABI for compatability of the logic call message - OutgoingLogicCallABIJSON = `[{ + OutgoingLogicCallABI, err = abi.JSON(strings.NewReader(`[{ "name": "checkpoint", "outputs": [], "stateMutability": "pure", @@ -72,5 +93,10 @@ const ( { "internalType": "bytes32", "name": "_invalidationId", "type": "bytes32" }, { "internalType": "uint256", "name": "_invalidationNonce", "type": "uint256" } ] - }]` -) + }]`)) + + if err != nil { + panic(fmt.Sprintf("can't decode OutgoingLogicCallABI, %s", err)) + } + +} diff --git a/module/x/gravity/types/batch.go b/module/x/gravity/types/batch.go index ef30d5a67..bcc53c16d 100644 --- a/module/x/gravity/types/batch.go +++ b/module/x/gravity/types/batch.go @@ -3,12 +3,9 @@ package types import ( "fmt" "math/big" - "strings" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -205,12 +202,6 @@ func (o OutgoingTxBatch) GetCheckpoint(gravityIDstring string) []byte { // GetCheckpoint gets the checkpoint signature from the given outgoing tx batch func (i InternalOutgoingTxBatch) GetCheckpoint(gravityIDstring string) []byte { - - abi, err := abi.JSON(strings.NewReader(OutgoingBatchTxCheckpointABIJSON)) - if err != nil { - panic("Bad ABI constant!") - } - // the contract argument is not a arbitrary length array but a fixed length 32 byte // array, therefore we have to utf8 encode the string (the default in this case) and // then copy the variable length encoded data into a fixed length array. This function @@ -238,7 +229,7 @@ func (i InternalOutgoingTxBatch) GetCheckpoint(gravityIDstring string) []byte { // the methodName needs to be the same as the 'name' above in the checkpointAbiJson // but other than that it's a constant that has no impact on the output. This is because // it gets encoded as a function name which we must then discard. - abiEncodedBatch, err := abi.Pack("submitBatch", + abiEncodedBatch, err := OutgoingBatchTxCheckpointABI.Pack("submitBatch", gravityID, batchMethodName, txAmounts, @@ -263,12 +254,6 @@ func (i InternalOutgoingTxBatch) GetCheckpoint(gravityIDstring string) []byte { // GetCheckpoint gets the checkpoint signature from the given outgoing tx batch func (c OutgoingLogicCall) GetCheckpoint(gravityIDstring string) []byte { - - abi, err := abi.JSON(strings.NewReader(OutgoingLogicCallABIJSON)) - if err != nil { - panic("Bad ABI constant!") - } - // Create the methodName argument which salts the signature methodNameBytes := []uint8("logicCall") var logicCallMethodName [32]uint8 @@ -304,7 +289,7 @@ func (c OutgoingLogicCall) GetCheckpoint(gravityIDstring string) []byte { // the methodName needs to be the same as the 'name' above in the checkpointAbiJson // but other than that it's a constant that has no impact on the output. This is because // it gets encoded as a function name which we must then discard. - abiEncodedCall, err := abi.Pack("checkpoint", + abiEncodedCall, err := OutgoingLogicCallABI.Pack("checkpoint", gravityID, logicCallMethodName, transferAmounts, diff --git a/module/x/gravity/types/msgs.go b/module/x/gravity/types/msgs.go index 1875eefa6..b6b2cd628 100644 --- a/module/x/gravity/types/msgs.go +++ b/module/x/gravity/types/msgs.go @@ -601,11 +601,6 @@ func (e *MsgValsetUpdatedClaim) ValidateBasic() error { return fmt.Errorf("nonce == 0") } - err := ValidateEthAddress(e.RewardToken) - if err != nil { - return err - } - for _, member := range e.Members { err := ValidateEthAddress(member.EthereumAddress) if err != nil { @@ -659,7 +654,7 @@ func (b *MsgValsetUpdatedClaim) ClaimHash() ([]byte, error) { return nil, sdkerrors.Wrap(err, "invalid members") } internalMembers.Sort() - path := fmt.Sprintf("%d/%d/%d/%x/%s/%s", b.EventNonce, b.ValsetNonce, b.BlockHeight, internalMembers.ToExternal(), b.RewardAmount.String(), b.RewardToken) + path := fmt.Sprintf("%d/%d/%d/%x/%s/%s/%s", b.EventNonce, b.ValsetNonce, b.BlockHeight, internalMembers.ToExternal(), b.RewardAmount.String(), b.RewardDenom, b.RewardRecipient) return tmhash.Sum([]byte(path)), nil } diff --git a/module/x/gravity/types/msgs.pb.go b/module/x/gravity/types/msgs.pb.go index 3511951e9..91d1c6844 100644 --- a/module/x/gravity/types/msgs.pb.go +++ b/module/x/gravity/types/msgs.pb.go @@ -1210,13 +1210,14 @@ var xxx_messageInfo_MsgLogicCallExecutedClaimResponse proto.InternalMessageInfo // This informs the Cosmos module that a validator // set has been updated. type MsgValsetUpdatedClaim struct { - EventNonce uint64 `protobuf:"varint,1,opt,name=event_nonce,json=eventNonce,proto3" json:"event_nonce,omitempty"` - ValsetNonce uint64 `protobuf:"varint,2,opt,name=valset_nonce,json=valsetNonce,proto3" json:"valset_nonce,omitempty"` - BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - Members []BridgeValidator `protobuf:"bytes,4,rep,name=members,proto3" json:"members"` - RewardAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=reward_amount,json=rewardAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"reward_amount"` - RewardToken string `protobuf:"bytes,6,opt,name=reward_token,json=rewardToken,proto3" json:"reward_token,omitempty"` - Orchestrator string `protobuf:"bytes,7,opt,name=orchestrator,proto3" json:"orchestrator,omitempty"` + EventNonce uint64 `protobuf:"varint,1,opt,name=event_nonce,json=eventNonce,proto3" json:"event_nonce,omitempty"` + ValsetNonce uint64 `protobuf:"varint,2,opt,name=valset_nonce,json=valsetNonce,proto3" json:"valset_nonce,omitempty"` + BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + Members []BridgeValidator `protobuf:"bytes,4,rep,name=members,proto3" json:"members"` + RewardAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=reward_amount,json=rewardAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"reward_amount"` + RewardDenom string `protobuf:"bytes,6,opt,name=reward_denom,json=rewardDenom,proto3" json:"reward_denom,omitempty"` + RewardRecipient string `protobuf:"bytes,7,opt,name=reward_recipient,json=rewardRecipient,proto3" json:"reward_recipient,omitempty"` + Orchestrator string `protobuf:"bytes,8,opt,name=orchestrator,proto3" json:"orchestrator,omitempty"` } func (m *MsgValsetUpdatedClaim) Reset() { *m = MsgValsetUpdatedClaim{} } @@ -1280,9 +1281,16 @@ func (m *MsgValsetUpdatedClaim) GetMembers() []BridgeValidator { return nil } -func (m *MsgValsetUpdatedClaim) GetRewardToken() string { +func (m *MsgValsetUpdatedClaim) GetRewardDenom() string { if m != nil { - return m.RewardToken + return m.RewardDenom + } + return "" +} + +func (m *MsgValsetUpdatedClaim) GetRewardRecipient() string { + if m != nil { + return m.RewardRecipient } return "" } @@ -1553,106 +1561,106 @@ func init() { func init() { proto.RegisterFile("gravity/v1/msgs.proto", fileDescriptor_2f8523f2f6feb451) } var fileDescriptor_2f8523f2f6feb451 = []byte{ - // 1571 bytes of a gzipped FileDescriptorProto + // 1584 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x6f, 0xdc, 0x44, - 0x14, 0x8f, 0x93, 0x4d, 0xd2, 0xbc, 0xcd, 0x47, 0xeb, 0xa6, 0xe9, 0xc6, 0x49, 0x37, 0x89, 0xdb, - 0x7c, 0x94, 0x92, 0xdd, 0x26, 0x1c, 0x38, 0x20, 0x81, 0xba, 0x69, 0x2a, 0x2a, 0x91, 0x22, 0x6d, - 0x4a, 0x0f, 0x5c, 0x2c, 0xaf, 0x3d, 0xf5, 0x9a, 0xda, 0x9e, 0xe0, 0x99, 0xdd, 0x36, 0x97, 0x4a, - 0xc0, 0x09, 0x95, 0x03, 0x1f, 0x27, 0x24, 0xf8, 0x13, 0x10, 0x17, 0x4e, 0x5c, 0xb8, 0x56, 0x1c, - 0x50, 0x25, 0x0e, 0x20, 0x90, 0x2a, 0xd4, 0xf2, 0x87, 0x20, 0xcf, 0x8c, 0x27, 0xb3, 0xb6, 0x37, - 0x59, 0x50, 0x38, 0x65, 0xe7, 0xcd, 0x9b, 0x79, 0xbf, 0xf7, 0x7b, 0x6f, 0xde, 0x7b, 0x31, 0x5c, - 0xf0, 0x62, 0xbb, 0xeb, 0xd3, 0xc3, 0x7a, 0x77, 0xab, 0x1e, 0x12, 0x8f, 0xd4, 0x0e, 0x62, 0x4c, - 0xb1, 0x0e, 0x42, 0x5c, 0xeb, 0x6e, 0x19, 0x55, 0x07, 0x93, 0x10, 0x93, 0x7a, 0xcb, 0x26, 0xa8, - 0xde, 0xdd, 0x6a, 0x21, 0x6a, 0x6f, 0xd5, 0x1d, 0xec, 0x47, 0x5c, 0xd7, 0x98, 0xf5, 0xb0, 0x87, - 0xd9, 0xcf, 0x7a, 0xf2, 0x4b, 0x48, 0x17, 0x3d, 0x8c, 0xbd, 0x00, 0xd5, 0xed, 0x03, 0xbf, 0x6e, - 0x47, 0x11, 0xa6, 0x36, 0xf5, 0x71, 0x24, 0xee, 0x37, 0xe6, 0x14, 0xb3, 0xf4, 0xf0, 0x00, 0xa5, - 0xf2, 0x79, 0x71, 0x8a, 0xad, 0x5a, 0x9d, 0xfb, 0x75, 0x3b, 0x3a, 0x4c, 0xb7, 0x38, 0x0c, 0x8b, - 0x5b, 0xe2, 0x0b, 0xbe, 0x65, 0x3e, 0x86, 0xf9, 0x3d, 0xe2, 0xed, 0x23, 0xfa, 0x6e, 0xec, 0xb4, - 0x11, 0xa1, 0xb1, 0x4d, 0x71, 0x7c, 0xc3, 0x75, 0x63, 0x44, 0x88, 0xbe, 0x08, 0x13, 0x5d, 0x3b, - 0xf0, 0xdd, 0x44, 0x56, 0xd1, 0x96, 0xb5, 0x8d, 0x89, 0xe6, 0x91, 0x40, 0x37, 0x61, 0x12, 0x2b, - 0x87, 0x2a, 0xc3, 0x4c, 0xa1, 0x47, 0xa6, 0x2f, 0x41, 0x19, 0xd1, 0xb6, 0x65, 0xf3, 0x0b, 0x2b, - 0x23, 0x4c, 0x05, 0x10, 0x6d, 0x0b, 0x13, 0xe6, 0x65, 0x58, 0xe9, 0x6b, 0xbf, 0x89, 0xc8, 0x01, - 0x8e, 0x08, 0x32, 0x9f, 0x68, 0x70, 0x76, 0x8f, 0x78, 0xf7, 0xec, 0x80, 0x20, 0xba, 0x83, 0xa3, - 0xfb, 0x7e, 0x1c, 0xea, 0xb3, 0x30, 0x1a, 0xe1, 0xc8, 0x41, 0x0c, 0x58, 0xa9, 0xc9, 0x17, 0xa7, - 0x02, 0x2a, 0xf1, 0x9b, 0xf8, 0x5e, 0x64, 0xd3, 0x4e, 0x8c, 0x2a, 0x25, 0xee, 0xb7, 0x14, 0x98, - 0x06, 0x54, 0xb2, 0x60, 0x24, 0xd2, 0x1f, 0x35, 0x98, 0x64, 0xfe, 0x44, 0xee, 0x5d, 0xbc, 0x4b, - 0xdb, 0xfa, 0x1c, 0x8c, 0x11, 0x14, 0xb9, 0x28, 0xe5, 0x4f, 0xac, 0xf4, 0x79, 0x38, 0x93, 0x60, - 0x70, 0x11, 0xa1, 0x02, 0xe3, 0x38, 0xa2, 0xed, 0x9b, 0x88, 0x50, 0xfd, 0x75, 0x18, 0xb3, 0x43, - 0xdc, 0x89, 0x28, 0x43, 0x56, 0xde, 0x9e, 0xaf, 0x89, 0x88, 0x25, 0x59, 0x54, 0x13, 0x59, 0x54, - 0xdb, 0xc1, 0x7e, 0xd4, 0x28, 0x3d, 0x7d, 0xbe, 0x34, 0xd4, 0x14, 0xea, 0xfa, 0x9b, 0x00, 0xad, - 0xd8, 0x77, 0x3d, 0x64, 0xdd, 0x47, 0x1c, 0xf7, 0x00, 0x87, 0x27, 0xf8, 0x91, 0x5b, 0x08, 0x99, - 0x73, 0x30, 0xab, 0x62, 0x97, 0x4e, 0xbd, 0x05, 0x33, 0x7b, 0xc4, 0x6b, 0xa2, 0x0f, 0x3b, 0x88, - 0xd0, 0x86, 0x4d, 0x9d, 0xfe, 0x6e, 0xcd, 0xc2, 0xa8, 0x8b, 0x22, 0x1c, 0x0a, 0x9f, 0xf8, 0xc2, - 0x9c, 0x87, 0x8b, 0x99, 0x0b, 0xe4, 0xdd, 0xdf, 0x6b, 0xec, 0x72, 0xc1, 0x23, 0xbf, 0xbc, 0x38, - 0xb2, 0xab, 0x30, 0x4d, 0xf1, 0x03, 0x14, 0x59, 0x0e, 0x8e, 0x68, 0x6c, 0x3b, 0x29, 0x6f, 0x53, - 0x4c, 0xba, 0x23, 0x84, 0xfa, 0x25, 0x48, 0x22, 0x69, 0x25, 0xe1, 0x42, 0xb1, 0x88, 0xed, 0x04, - 0xa2, 0xed, 0x7d, 0x26, 0xc8, 0xe5, 0x47, 0xa9, 0x20, 0x3f, 0x7a, 0xc2, 0x3f, 0x9a, 0x0d, 0x3f, - 0x77, 0x46, 0x05, 0x2c, 0x9d, 0xf9, 0x45, 0x83, 0xf3, 0x47, 0x7b, 0xef, 0x60, 0xcf, 0x77, 0x76, - 0xec, 0x20, 0xd0, 0xd7, 0x61, 0xc6, 0x8f, 0xc4, 0xc3, 0xf1, 0x71, 0x64, 0xf9, 0xae, 0xa0, 0x6d, - 0x5a, 0x15, 0xdf, 0x76, 0xf5, 0x4d, 0xd0, 0x7b, 0x14, 0x39, 0x0d, 0xc3, 0x8c, 0x86, 0x73, 0xea, - 0xce, 0x1d, 0x46, 0xc9, 0xff, 0xee, 0xeb, 0x25, 0x58, 0x28, 0xf0, 0x47, 0xfa, 0xfb, 0xd3, 0xb0, - 0x92, 0x31, 0x3b, 0x2c, 0xcf, 0x76, 0x02, 0xdb, 0x0f, 0xd9, 0x0b, 0xeb, 0xa2, 0x88, 0x5a, 0x6a, - 0x1c, 0x81, 0x89, 0x38, 0xf2, 0x15, 0x98, 0x6c, 0x05, 0xd8, 0x79, 0x60, 0xb5, 0x91, 0xef, 0xb5, - 0xa9, 0x70, 0xb1, 0xcc, 0x64, 0x6f, 0x33, 0x51, 0x41, 0xbc, 0x47, 0x8a, 0xe2, 0x7d, 0x4b, 0xbe, - 0x16, 0xe6, 0x5e, 0xa3, 0x96, 0x64, 0xf5, 0x1f, 0xcf, 0x97, 0xd6, 0x3c, 0x9f, 0xb6, 0x3b, 0xad, - 0x9a, 0x83, 0x43, 0x51, 0xf1, 0xc4, 0x9f, 0x4d, 0xe2, 0x3e, 0x10, 0x85, 0xf3, 0x76, 0x44, 0xe5, - 0xe3, 0x59, 0x87, 0x19, 0x44, 0xdb, 0x28, 0x46, 0x9d, 0xd0, 0x12, 0xa9, 0xcd, 0xe9, 0x98, 0x4e, - 0xc5, 0xfb, 0x3c, 0xc5, 0xd7, 0x61, 0x46, 0x94, 0xd3, 0x18, 0x39, 0xc8, 0xef, 0xa2, 0xb8, 0x32, - 0xc6, 0x15, 0xb9, 0xb8, 0x29, 0xa4, 0x39, 0xfa, 0xc7, 0xf3, 0xf4, 0x9b, 0x55, 0x58, 0x2c, 0x22, - 0x50, 0x32, 0xfc, 0x54, 0x83, 0xb9, 0x3d, 0xe2, 0xb1, 0x34, 0x93, 0x0f, 0xf3, 0xf4, 0x38, 0x5e, - 0x82, 0x72, 0x2b, 0xb9, 0x5a, 0xdc, 0x31, 0xc2, 0xef, 0x60, 0xa2, 0x3b, 0x7d, 0x1e, 0x5d, 0xa9, - 0x28, 0x08, 0x59, 0x57, 0x47, 0x0b, 0x5c, 0x5d, 0x86, 0x6a, 0xb1, 0x27, 0xd2, 0xd9, 0x2f, 0x86, - 0xe1, 0xc2, 0x1e, 0xf1, 0x76, 0x9b, 0x3b, 0xdb, 0xd7, 0x6f, 0xa2, 0x83, 0x00, 0x1f, 0x22, 0xf7, - 0xf4, 0x7c, 0x5d, 0x81, 0x49, 0x11, 0x37, 0x5e, 0xa1, 0x78, 0x36, 0x95, 0xb9, 0xec, 0x66, 0x22, - 0x1a, 0xd4, 0x5b, 0x1d, 0x4a, 0x91, 0x1d, 0xa6, 0xcf, 0x85, 0xfd, 0x66, 0x05, 0xf1, 0x30, 0x6c, - 0xe1, 0x40, 0x24, 0x83, 0x58, 0xe9, 0x06, 0x9c, 0x71, 0x91, 0xe3, 0x87, 0x76, 0x40, 0x58, 0x02, - 0x94, 0x9a, 0x72, 0x9d, 0x63, 0xed, 0x4c, 0x01, 0x6b, 0x4b, 0x70, 0xa9, 0x90, 0x12, 0x49, 0xda, - 0x9f, 0x1a, 0xeb, 0xe0, 0xf2, 0x71, 0xee, 0x3e, 0x42, 0x4e, 0x87, 0x9e, 0x26, 0x71, 0x05, 0xd5, - 0x2b, 0xe1, 0x6e, 0x72, 0xc0, 0xea, 0x55, 0xea, 0x57, 0xbd, 0x06, 0x49, 0x1a, 0x3e, 0x1e, 0x14, - 0x3b, 0x27, 0x29, 0xf8, 0x8d, 0xe7, 0x0d, 0xef, 0xc8, 0xef, 0x1d, 0xb8, 0xf6, 0xbf, 0x72, 0xbf, - 0xcb, 0x8e, 0xf5, 0x94, 0xda, 0x32, 0x97, 0x15, 0x33, 0x34, 0x92, 0x67, 0xe8, 0x0d, 0x18, 0x0f, - 0x51, 0xd8, 0x42, 0x31, 0xa9, 0x94, 0x96, 0x47, 0x36, 0xca, 0xdb, 0x0b, 0xb5, 0xa3, 0x21, 0xb0, - 0xd6, 0x60, 0x0d, 0xf6, 0x5e, 0x3a, 0x37, 0x89, 0xbe, 0x9b, 0x9e, 0xd0, 0xf7, 0x61, 0x2a, 0x46, - 0x0f, 0xed, 0xd8, 0xb5, 0x44, 0x1d, 0x1b, 0xfd, 0x4f, 0x75, 0x6c, 0x92, 0x5f, 0x72, 0x83, 0x57, - 0xb3, 0x15, 0x10, 0x6b, 0x8b, 0xa5, 0xae, 0x48, 0xca, 0x32, 0x97, 0xdd, 0x4d, 0x44, 0x03, 0x95, - 0x27, 0x9e, 0x7d, 0x79, 0x62, 0x25, 0xf5, 0xfb, 0xa0, 0x27, 0x0d, 0xc2, 0x8e, 0x1c, 0x14, 0x1c, - 0x0d, 0x3d, 0xc9, 0x3b, 0x8a, 0xed, 0x88, 0xd8, 0x8e, 0xda, 0xee, 0x4a, 0xcd, 0x29, 0x45, 0x7a, - 0xdb, 0x55, 0x86, 0x88, 0x61, 0x75, 0x88, 0x30, 0x17, 0xc1, 0xc8, 0x5f, 0x2a, 0x4d, 0x7e, 0xad, - 0x31, 0x50, 0xfb, 0x9d, 0x56, 0xe8, 0xd3, 0x86, 0xed, 0xee, 0xa7, 0xdd, 0x6a, 0xb7, 0xeb, 0xbb, - 0x28, 0x89, 0x58, 0x03, 0xc6, 0x49, 0xa7, 0xf5, 0x01, 0x72, 0x28, 0xb3, 0x5b, 0xde, 0x9e, 0xad, - 0xf1, 0xd9, 0xb8, 0x96, 0xce, 0xc6, 0xb5, 0x1b, 0xd1, 0x61, 0x43, 0xff, 0xf9, 0x87, 0xcd, 0xe9, - 0xdd, 0xb4, 0xb8, 0x27, 0x2d, 0xd3, 0x6d, 0xa6, 0x07, 0x7b, 0xfb, 0xe2, 0x70, 0xa6, 0x2f, 0x2a, - 0xc8, 0x47, 0x7a, 0x90, 0xaf, 0xc3, 0xea, 0xb1, 0xd0, 0x52, 0x27, 0xb6, 0x3f, 0x99, 0x86, 0x91, - 0x3d, 0xe2, 0xe9, 0x0f, 0x61, 0xaa, 0x77, 0xaa, 0x5d, 0x54, 0x33, 0x27, 0x3b, 0x66, 0x1a, 0x57, - 0x8e, 0xdb, 0x95, 0x0c, 0x99, 0x1f, 0xff, 0xfa, 0xf7, 0x57, 0xc3, 0x8b, 0xa6, 0x51, 0x57, 0xfe, - 0x55, 0x10, 0x69, 0xee, 0x08, 0x3b, 0x6d, 0x98, 0x38, 0x8a, 0x57, 0x25, 0x73, 0xad, 0xdc, 0x31, - 0x96, 0xfb, 0xed, 0x48, 0x63, 0x4b, 0xcc, 0xd8, 0xbc, 0x79, 0x51, 0x35, 0x96, 0xd0, 0x61, 0x51, - 0x6c, 0x21, 0xda, 0xd6, 0x09, 0x4c, 0xf6, 0x8c, 0x8e, 0x0b, 0x99, 0x2b, 0xd5, 0x4d, 0xe3, 0xf2, - 0x31, 0x9b, 0xd2, 0xe4, 0x0a, 0x33, 0xb9, 0x60, 0xce, 0xab, 0x26, 0x63, 0xae, 0x69, 0xb1, 0xe6, - 0x95, 0x18, 0xed, 0x19, 0x29, 0xb3, 0x46, 0xd5, 0xcd, 0x9c, 0xd1, 0xc2, 0xd9, 0xae, 0xd0, 0xa8, - 0x60, 0x53, 0x18, 0x7d, 0x0c, 0x67, 0x73, 0xa3, 0xdf, 0x52, 0xf1, 0xdd, 0x52, 0xc1, 0x58, 0x3f, - 0x41, 0x41, 0x02, 0x58, 0x66, 0x00, 0x0c, 0xb3, 0x92, 0x03, 0x10, 0x5a, 0x41, 0xa2, 0xad, 0x7f, - 0xaa, 0xc1, 0xb9, 0xfc, 0x2c, 0x56, 0x1c, 0x42, 0x45, 0xc3, 0xd8, 0x38, 0x49, 0x43, 0x62, 0xd8, - 0x60, 0x18, 0x4c, 0x73, 0xb9, 0x28, 0xd8, 0xa2, 0xbb, 0x3a, 0xcc, 0xea, 0x97, 0x1a, 0x9c, 0x2f, - 0x9a, 0x5a, 0xcc, 0x8c, 0xad, 0x02, 0x1d, 0xe3, 0x95, 0x93, 0x75, 0x24, 0xa2, 0x6b, 0x0c, 0xd1, - 0xaa, 0x79, 0x59, 0x45, 0xc4, 0x67, 0x1a, 0x25, 0x09, 0x05, 0xa8, 0x27, 0x1a, 0x9c, 0x53, 0x8b, - 0x19, 0x87, 0xb4, 0x52, 0xf8, 0xa8, 0xd4, 0x72, 0x67, 0x5c, 0x3d, 0x51, 0xe5, 0x78, 0x8a, 0xc4, - 0xe3, 0xeb, 0xf0, 0x03, 0x02, 0xcd, 0x67, 0x1a, 0xe8, 0x05, 0xb3, 0x4e, 0x16, 0x4e, 0x5e, 0x25, - 0x07, 0xe7, 0x98, 0xf1, 0xa0, 0x10, 0x0e, 0x8a, 0x9d, 0xed, 0xeb, 0x96, 0x2b, 0x0e, 0x08, 0x38, - 0xdf, 0x6a, 0x30, 0xd7, 0x67, 0x8a, 0x58, 0xcd, 0xd8, 0x2b, 0x56, 0x33, 0x36, 0x07, 0x52, 0x93, - 0xd0, 0x36, 0x19, 0xb4, 0x75, 0x73, 0x55, 0x85, 0xc6, 0x32, 0xd9, 0x72, 0xec, 0x20, 0xb0, 0x90, - 0x38, 0x25, 0xf0, 0x7d, 0xa3, 0xc1, 0x5c, 0x9f, 0xef, 0x14, 0xab, 0xb9, 0x04, 0x2e, 0x52, 0xcb, - 0xe1, 0x3b, 0xe1, 0xab, 0xc3, 0xab, 0x0c, 0xdf, 0x9a, 0x79, 0xa5, 0x37, 0xd9, 0xa9, 0xa5, 0xb6, - 0xc8, 0xf4, 0x2b, 0x82, 0xfe, 0x91, 0x06, 0x33, 0xd9, 0x3e, 0x58, 0xcd, 0xbe, 0xed, 0xde, 0x7d, - 0x63, 0xed, 0xf8, 0x7d, 0x89, 0x64, 0x8d, 0x21, 0x59, 0x36, 0xab, 0x3d, 0x4f, 0x9f, 0x29, 0xab, - 0x59, 0xae, 0x7f, 0xa7, 0x81, 0x71, 0x4c, 0x5f, 0xcc, 0xa6, 0x4d, 0x7f, 0x55, 0x63, 0x6b, 0x60, - 0x55, 0x09, 0x72, 0x8b, 0x81, 0xbc, 0x66, 0x5e, 0xed, 0xa1, 0x8b, 0x9d, 0xb3, 0x5a, 0xb6, 0x6b, - 0xc9, 0xee, 0x69, 0x21, 0x71, 0xb4, 0x61, 0x3d, 0x7d, 0x51, 0xd5, 0x9e, 0xbd, 0xa8, 0x6a, 0x7f, - 0xbd, 0xa8, 0x6a, 0x9f, 0xbf, 0xac, 0x0e, 0x3d, 0x7b, 0x59, 0x1d, 0xfa, 0xfd, 0x65, 0x75, 0xe8, - 0xfd, 0x5d, 0x65, 0xea, 0xc1, 0x11, 0x0e, 0x0f, 0x59, 0xe7, 0x76, 0x70, 0x90, 0x0e, 0x3f, 0xc2, - 0xc6, 0x26, 0xff, 0x7a, 0x51, 0x0f, 0xb1, 0xdb, 0x09, 0x50, 0xfd, 0x91, 0xb4, 0xcd, 0x06, 0xa3, - 0xd6, 0x18, 0x3b, 0xf6, 0xda, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x25, 0x7d, 0x77, 0xae, 0xab, - 0x13, 0x00, 0x00, + 0x14, 0x8f, 0xb3, 0x9b, 0xa4, 0x79, 0x9b, 0x8f, 0xd6, 0x4d, 0xd3, 0x8d, 0x93, 0x6e, 0x12, 0xb7, + 0xf9, 0x28, 0x25, 0xbb, 0x4d, 0x38, 0x70, 0x40, 0x02, 0x75, 0xd3, 0x54, 0x54, 0x22, 0x45, 0xda, + 0x40, 0x0f, 0x5c, 0x2c, 0xaf, 0x3d, 0xf5, 0x9a, 0xda, 0x9e, 0xe0, 0x99, 0xdd, 0x36, 0x97, 0x4a, + 0x94, 0x13, 0x2a, 0x07, 0x3e, 0x4e, 0x48, 0xf0, 0x27, 0x20, 0x2e, 0x9c, 0xb8, 0x70, 0xad, 0x38, + 0xa0, 0x4a, 0x5c, 0x10, 0x48, 0x15, 0x6a, 0xf9, 0x43, 0x90, 0x67, 0xc6, 0x93, 0x59, 0xdb, 0x9b, + 0xa4, 0x28, 0x9c, 0xb2, 0xf3, 0xe6, 0xcd, 0x7b, 0xbf, 0xf7, 0xfd, 0x62, 0xb8, 0xe0, 0xc5, 0x76, + 0xcf, 0xa7, 0x07, 0x8d, 0xde, 0x66, 0x23, 0x24, 0x1e, 0xa9, 0xef, 0xc7, 0x98, 0x62, 0x1d, 0x04, + 0xb9, 0xde, 0xdb, 0x34, 0x6a, 0x0e, 0x26, 0x21, 0x26, 0x8d, 0xb6, 0x4d, 0x50, 0xa3, 0xb7, 0xd9, + 0x46, 0xd4, 0xde, 0x6c, 0x38, 0xd8, 0x8f, 0x38, 0xaf, 0x31, 0xe3, 0x61, 0x0f, 0xb3, 0x9f, 0x8d, + 0xe4, 0x97, 0xa0, 0x2e, 0x78, 0x18, 0x7b, 0x01, 0x6a, 0xd8, 0xfb, 0x7e, 0xc3, 0x8e, 0x22, 0x4c, + 0x6d, 0xea, 0xe3, 0x48, 0xc8, 0x37, 0x66, 0x15, 0xb5, 0xf4, 0x60, 0x1f, 0xa5, 0xf4, 0x39, 0xf1, + 0x8a, 0x9d, 0xda, 0xdd, 0x7b, 0x0d, 0x3b, 0x3a, 0x48, 0xaf, 0x38, 0x0c, 0x8b, 0x6b, 0xe2, 0x07, + 0x7e, 0x65, 0x3e, 0x82, 0xb9, 0x5d, 0xe2, 0xed, 0x21, 0xfa, 0x7e, 0xec, 0x74, 0x10, 0xa1, 0xb1, + 0x4d, 0x71, 0x7c, 0xc3, 0x75, 0x63, 0x44, 0x88, 0xbe, 0x00, 0xe3, 0x3d, 0x3b, 0xf0, 0xdd, 0x84, + 0x56, 0xd5, 0x96, 0xb4, 0xf5, 0xf1, 0xd6, 0x21, 0x41, 0x37, 0x61, 0x02, 0x2b, 0x8f, 0xaa, 0xc3, + 0x8c, 0xa1, 0x8f, 0xa6, 0x2f, 0x42, 0x05, 0xd1, 0x8e, 0x65, 0x73, 0x81, 0xd5, 0x12, 0x63, 0x01, + 0x44, 0x3b, 0x42, 0x85, 0x79, 0x19, 0x96, 0x07, 0xea, 0x6f, 0x21, 0xb2, 0x8f, 0x23, 0x82, 0xcc, + 0x27, 0x1a, 0x9c, 0xdd, 0x25, 0xde, 0x5d, 0x3b, 0x20, 0x88, 0x6e, 0xe3, 0xe8, 0x9e, 0x1f, 0x87, + 0xfa, 0x0c, 0x8c, 0x44, 0x38, 0x72, 0x10, 0x03, 0x56, 0x6e, 0xf1, 0xc3, 0xa9, 0x80, 0x4a, 0xec, + 0x26, 0xbe, 0x17, 0xd9, 0xb4, 0x1b, 0xa3, 0x6a, 0x99, 0xdb, 0x2d, 0x09, 0xa6, 0x01, 0xd5, 0x2c, + 0x18, 0x89, 0xf4, 0x67, 0x0d, 0x26, 0x98, 0x3d, 0x91, 0xfb, 0x01, 0xde, 0xa1, 0x1d, 0x7d, 0x16, + 0x46, 0x09, 0x8a, 0x5c, 0x94, 0xfa, 0x4f, 0x9c, 0xf4, 0x39, 0x38, 0x93, 0x60, 0x70, 0x11, 0xa1, + 0x02, 0xe3, 0x18, 0xa2, 0x9d, 0x9b, 0x88, 0x50, 0xfd, 0x4d, 0x18, 0xb5, 0x43, 0xdc, 0x8d, 0x28, + 0x43, 0x56, 0xd9, 0x9a, 0xab, 0x8b, 0x88, 0x25, 0x59, 0x54, 0x17, 0x59, 0x54, 0xdf, 0xc6, 0x7e, + 0xd4, 0x2c, 0x3f, 0x7d, 0xbe, 0x38, 0xd4, 0x12, 0xec, 0xfa, 0xdb, 0x00, 0xed, 0xd8, 0x77, 0x3d, + 0x64, 0xdd, 0x43, 0x1c, 0xf7, 0x09, 0x1e, 0x8f, 0xf3, 0x27, 0xb7, 0x10, 0x32, 0x67, 0x61, 0x46, + 0xc5, 0x2e, 0x8d, 0x7a, 0x07, 0xa6, 0x77, 0x89, 0xd7, 0x42, 0x9f, 0x74, 0x11, 0xa1, 0x4d, 0x9b, + 0x3a, 0x83, 0xcd, 0x9a, 0x81, 0x11, 0x17, 0x45, 0x38, 0x14, 0x36, 0xf1, 0x83, 0x39, 0x07, 0x17, + 0x33, 0x02, 0xa4, 0xec, 0x1f, 0x35, 0x26, 0x5c, 0xf8, 0x91, 0x0b, 0x2f, 0x8e, 0xec, 0x0a, 0x4c, + 0x51, 0x7c, 0x1f, 0x45, 0x96, 0x83, 0x23, 0x1a, 0xdb, 0x4e, 0xea, 0xb7, 0x49, 0x46, 0xdd, 0x16, + 0x44, 0xfd, 0x12, 0x24, 0x91, 0xb4, 0x92, 0x70, 0xa1, 0x58, 0xc4, 0x76, 0x1c, 0xd1, 0xce, 0x1e, + 0x23, 0xe4, 0xf2, 0xa3, 0x5c, 0x90, 0x1f, 0x7d, 0xe1, 0x1f, 0xc9, 0x86, 0x9f, 0x1b, 0xa3, 0x02, + 0x96, 0xc6, 0xfc, 0xa6, 0xc1, 0xf9, 0xc3, 0xbb, 0xf7, 0xb0, 0xe7, 0x3b, 0xdb, 0x76, 0x10, 0xe8, + 0x6b, 0x30, 0xed, 0x47, 0xa2, 0x70, 0x7c, 0x1c, 0x59, 0xbe, 0x2b, 0xdc, 0x36, 0xa5, 0x92, 0x6f, + 0xbb, 0xfa, 0x06, 0xe8, 0x7d, 0x8c, 0xdc, 0x0d, 0xc3, 0xcc, 0x0d, 0xe7, 0xd4, 0x9b, 0x3b, 0xcc, + 0x25, 0xff, 0xbb, 0xad, 0x97, 0x60, 0xbe, 0xc0, 0x1e, 0x69, 0xef, 0x2f, 0xc3, 0x4a, 0xc6, 0x6c, + 0xb3, 0x3c, 0xdb, 0x0e, 0x6c, 0x3f, 0x64, 0x15, 0xd6, 0x43, 0x11, 0xb5, 0xd4, 0x38, 0x02, 0x23, + 0x71, 0xe4, 0xcb, 0x30, 0xd1, 0x0e, 0xb0, 0x73, 0xdf, 0xea, 0x20, 0xdf, 0xeb, 0x50, 0x61, 0x62, + 0x85, 0xd1, 0xde, 0x65, 0xa4, 0x82, 0x78, 0x97, 0x8a, 0xe2, 0x7d, 0x4b, 0x56, 0x0b, 0x33, 0xaf, + 0x59, 0x4f, 0xb2, 0xfa, 0xcf, 0xe7, 0x8b, 0xab, 0x9e, 0x4f, 0x3b, 0xdd, 0x76, 0xdd, 0xc1, 0xa1, + 0xe8, 0x78, 0xe2, 0xcf, 0x06, 0x71, 0xef, 0x8b, 0xc6, 0x79, 0x3b, 0xa2, 0xb2, 0x78, 0xd6, 0x60, + 0x1a, 0xd1, 0x0e, 0x8a, 0x51, 0x37, 0xb4, 0x44, 0x6a, 0x73, 0x77, 0x4c, 0xa5, 0xe4, 0x3d, 0x9e, + 0xe2, 0x6b, 0x30, 0x2d, 0xda, 0x69, 0x8c, 0x1c, 0xe4, 0xf7, 0x50, 0x5c, 0x1d, 0xe5, 0x8c, 0x9c, + 0xdc, 0x12, 0xd4, 0x9c, 0xfb, 0xc7, 0xf2, 0xee, 0x37, 0x6b, 0xb0, 0x50, 0xe4, 0x40, 0xe9, 0xe1, + 0xa7, 0x1a, 0xcc, 0xee, 0x12, 0x8f, 0xa5, 0x99, 0x2c, 0xcc, 0xd3, 0xf3, 0xf1, 0x22, 0x54, 0xda, + 0x89, 0x68, 0x21, 0xa3, 0xc4, 0x65, 0x30, 0xd2, 0x9d, 0x01, 0x45, 0x57, 0x2e, 0x0a, 0x42, 0xd6, + 0xd4, 0x91, 0x02, 0x53, 0x97, 0xa0, 0x56, 0x6c, 0x89, 0x34, 0xf6, 0xab, 0x61, 0xb8, 0xb0, 0x4b, + 0xbc, 0x9d, 0xd6, 0xf6, 0xd6, 0xf5, 0x9b, 0x68, 0x3f, 0xc0, 0x07, 0xc8, 0x3d, 0x3d, 0x5b, 0x97, + 0x61, 0x42, 0xc4, 0x8d, 0x77, 0x28, 0x9e, 0x4d, 0x15, 0x4e, 0xbb, 0x99, 0x90, 0x4e, 0x6a, 0xad, + 0x0e, 0xe5, 0xc8, 0x0e, 0xd3, 0x72, 0x61, 0xbf, 0x59, 0x43, 0x3c, 0x08, 0xdb, 0x38, 0x10, 0xc9, + 0x20, 0x4e, 0xba, 0x01, 0x67, 0x5c, 0xe4, 0xf8, 0xa1, 0x1d, 0x10, 0x96, 0x00, 0xe5, 0x96, 0x3c, + 0xe7, 0xbc, 0x76, 0xa6, 0xc0, 0x6b, 0x8b, 0x70, 0xa9, 0xd0, 0x25, 0xd2, 0x69, 0x7f, 0x69, 0x6c, + 0x82, 0xcb, 0xe2, 0xdc, 0x79, 0x88, 0x9c, 0x2e, 0x3d, 0x4d, 0xc7, 0x15, 0x74, 0xaf, 0xc4, 0x77, + 0x13, 0x27, 0xec, 0x5e, 0xe5, 0x41, 0xdd, 0xeb, 0x24, 0x49, 0xc3, 0xd7, 0x83, 0x62, 0xe3, 0xa4, + 0x0b, 0x1e, 0x97, 0x58, 0xde, 0xf0, 0x89, 0xfc, 0xe1, 0xbe, 0x6b, 0xbf, 0x92, 0xf9, 0x3d, 0xf6, + 0xac, 0xaf, 0xd5, 0x56, 0x38, 0xad, 0xd8, 0x43, 0xa5, 0xbc, 0x87, 0xde, 0x82, 0xb1, 0x10, 0x85, + 0x6d, 0x14, 0x93, 0x6a, 0x79, 0xa9, 0xb4, 0x5e, 0xd9, 0x9a, 0xaf, 0x1f, 0x2e, 0x81, 0xf5, 0x26, + 0x1b, 0xb0, 0x77, 0xd3, 0xbd, 0x49, 0xcc, 0xdd, 0xf4, 0x85, 0xbe, 0x07, 0x93, 0x31, 0x7a, 0x60, + 0xc7, 0xae, 0x25, 0xfa, 0xd8, 0xc8, 0x7f, 0xea, 0x63, 0x13, 0x5c, 0xc8, 0x0d, 0xde, 0xcd, 0x96, + 0x41, 0x9c, 0x45, 0xb2, 0xf3, 0xa4, 0xac, 0x70, 0x1a, 0x4f, 0xf6, 0xab, 0x70, 0x56, 0xb0, 0xc4, + 0xc8, 0xf1, 0xf7, 0x7d, 0x14, 0x51, 0xd1, 0xa2, 0xa6, 0x39, 0xbd, 0x95, 0x92, 0x5f, 0x21, 0x51, + 0xf3, 0x31, 0x90, 0x51, 0xda, 0x03, 0x3d, 0x99, 0x25, 0x76, 0xe4, 0xa0, 0xe0, 0x70, 0x3f, 0x4a, + 0x4a, 0x2e, 0xb6, 0x23, 0x62, 0x3b, 0xea, 0x64, 0x2c, 0xb7, 0x26, 0x15, 0xea, 0x6d, 0x57, 0xd9, + 0x37, 0x86, 0xd5, 0x7d, 0xc3, 0x5c, 0x00, 0x23, 0x2f, 0x54, 0xaa, 0xfc, 0x56, 0x63, 0xa0, 0xf6, + 0xba, 0xed, 0xd0, 0xa7, 0x4d, 0xdb, 0xdd, 0x4b, 0x07, 0xdb, 0x4e, 0xcf, 0x77, 0x51, 0x12, 0xdc, + 0x26, 0x8c, 0x91, 0x6e, 0xfb, 0x63, 0xe4, 0x50, 0xa6, 0xb7, 0xb2, 0x35, 0x53, 0xe7, 0x6b, 0x74, + 0x3d, 0x5d, 0xa3, 0xeb, 0x37, 0xa2, 0x83, 0xa6, 0xfe, 0xeb, 0x4f, 0x1b, 0x53, 0x3b, 0xe9, 0x1c, + 0x48, 0xa6, 0xab, 0xdb, 0x4a, 0x1f, 0xf6, 0x8f, 0xd0, 0xe1, 0xcc, 0x08, 0x55, 0x90, 0x97, 0xfa, + 0x90, 0xaf, 0xc1, 0xca, 0x91, 0xd0, 0x52, 0x23, 0xb6, 0x3e, 0x9b, 0x82, 0xd2, 0x2e, 0xf1, 0xf4, + 0x07, 0x30, 0xd9, 0xbf, 0x00, 0x2f, 0xa8, 0x49, 0x96, 0xdd, 0x48, 0x8d, 0x2b, 0x47, 0xdd, 0x4a, + 0x0f, 0x99, 0x8f, 0x7f, 0xff, 0xe7, 0x9b, 0xe1, 0x05, 0xd3, 0x68, 0x28, 0xff, 0x55, 0x88, 0x8a, + 0x70, 0x84, 0x9e, 0x0e, 0x8c, 0x1f, 0xc6, 0xab, 0x9a, 0x11, 0x2b, 0x6f, 0x8c, 0xa5, 0x41, 0x37, + 0x52, 0xd9, 0x22, 0x53, 0x36, 0x67, 0x5e, 0x54, 0x95, 0x25, 0xee, 0xb0, 0x28, 0xb6, 0x10, 0xed, + 0xe8, 0x04, 0x26, 0xfa, 0xb6, 0xcc, 0xf9, 0x8c, 0x48, 0xf5, 0xd2, 0xb8, 0x7c, 0xc4, 0xa5, 0x54, + 0xb9, 0xcc, 0x54, 0xce, 0x9b, 0x73, 0xaa, 0xca, 0x98, 0x73, 0x5a, 0x6c, 0xce, 0x25, 0x4a, 0xfb, + 0xb6, 0xcf, 0xac, 0x52, 0xf5, 0x32, 0xa7, 0xb4, 0x70, 0x0d, 0x2c, 0x54, 0x2a, 0xbc, 0x29, 0x94, + 0x3e, 0x82, 0xb3, 0xb9, 0x2d, 0x71, 0xb1, 0x58, 0xb6, 0x64, 0x30, 0xd6, 0x8e, 0x61, 0x90, 0x00, + 0x96, 0x18, 0x00, 0xc3, 0xac, 0xe6, 0x00, 0x84, 0x56, 0x90, 0x70, 0xeb, 0x9f, 0x6b, 0x70, 0x2e, + 0xbf, 0xb6, 0x15, 0x87, 0x50, 0xe1, 0x30, 0xd6, 0x8f, 0xe3, 0x90, 0x18, 0xd6, 0x19, 0x06, 0xd3, + 0x5c, 0x2a, 0x0a, 0xb6, 0x18, 0xc4, 0x0e, 0xd3, 0xfa, 0xb5, 0x06, 0xe7, 0x8b, 0x16, 0x1c, 0x33, + 0xa3, 0xab, 0x80, 0xc7, 0x78, 0xed, 0x78, 0x1e, 0x89, 0xe8, 0x1a, 0x43, 0xb4, 0x62, 0x5e, 0x56, + 0x11, 0xf1, 0xf5, 0x47, 0x49, 0x42, 0x01, 0xea, 0x89, 0x06, 0xe7, 0xd4, 0x66, 0xc6, 0x21, 0x2d, + 0x17, 0x16, 0x95, 0xda, 0xee, 0x8c, 0xab, 0xc7, 0xb2, 0x1c, 0xed, 0x22, 0x51, 0x7c, 0x5d, 0xfe, + 0x40, 0xa0, 0xf9, 0x42, 0x03, 0xbd, 0x60, 0x2d, 0xca, 0xc2, 0xc9, 0xb3, 0xe4, 0xe0, 0x1c, 0xb1, + 0x49, 0x14, 0xc2, 0x41, 0xb1, 0xb3, 0x75, 0xdd, 0x72, 0xc5, 0x03, 0x01, 0xe7, 0x7b, 0x0d, 0x66, + 0x07, 0x2c, 0x1c, 0x2b, 0x19, 0x7d, 0xc5, 0x6c, 0xc6, 0xc6, 0x89, 0xd8, 0x24, 0xb4, 0x0d, 0x06, + 0x6d, 0xcd, 0x5c, 0x51, 0xa1, 0xb1, 0x4c, 0xb6, 0x1c, 0x3b, 0x08, 0x2c, 0x24, 0x5e, 0x09, 0x7c, + 0xdf, 0x69, 0x30, 0x3b, 0xe0, 0x93, 0xc6, 0x4a, 0x2e, 0x81, 0x8b, 0xd8, 0x72, 0xf8, 0x8e, 0xf9, + 0x40, 0xf1, 0x3a, 0xc3, 0xb7, 0x6a, 0x5e, 0xe9, 0x4f, 0x76, 0x6a, 0xa9, 0x23, 0x32, 0xfd, 0xe0, + 0xa0, 0x7f, 0xaa, 0xc1, 0x74, 0x76, 0x0e, 0xd6, 0xb2, 0xb5, 0xdd, 0x7f, 0x6f, 0xac, 0x1e, 0x7d, + 0x2f, 0x91, 0xac, 0x32, 0x24, 0x4b, 0x66, 0xad, 0xaf, 0xf4, 0x19, 0xb3, 0x9a, 0xe5, 0xfa, 0x0f, + 0x1a, 0x18, 0x47, 0xcc, 0xc5, 0x6c, 0xda, 0x0c, 0x66, 0x35, 0x36, 0x4f, 0xcc, 0x2a, 0x41, 0x6e, + 0x32, 0x90, 0xd7, 0xcc, 0xab, 0x7d, 0xee, 0x62, 0xef, 0xac, 0xb6, 0xed, 0x5a, 0x72, 0x7a, 0x5a, + 0x48, 0x3c, 0x6d, 0x5a, 0x4f, 0x5f, 0xd4, 0xb4, 0x67, 0x2f, 0x6a, 0xda, 0xdf, 0x2f, 0x6a, 0xda, + 0x97, 0x2f, 0x6b, 0x43, 0xcf, 0x5e, 0xd6, 0x86, 0xfe, 0x78, 0x59, 0x1b, 0xfa, 0x68, 0x47, 0x59, + 0x90, 0x70, 0x84, 0xc3, 0x03, 0x36, 0xb9, 0x1d, 0x1c, 0xa4, 0x7b, 0x92, 0xd0, 0xb1, 0xc1, 0x3f, + 0x74, 0x34, 0x42, 0xec, 0x76, 0x03, 0xd4, 0x78, 0x28, 0x75, 0xb3, 0x1d, 0xaa, 0x3d, 0xca, 0x9e, + 0xbd, 0xf1, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xad, 0xbd, 0xfc, 0xf0, 0xd6, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2969,12 +2977,19 @@ func (m *MsgValsetUpdatedClaim) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.Orchestrator) i = encodeVarintMsgs(dAtA, i, uint64(len(m.Orchestrator))) i-- + dAtA[i] = 0x42 + } + if len(m.RewardRecipient) > 0 { + i -= len(m.RewardRecipient) + copy(dAtA[i:], m.RewardRecipient) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.RewardRecipient))) + i-- dAtA[i] = 0x3a } - if len(m.RewardToken) > 0 { - i -= len(m.RewardToken) - copy(dAtA[i:], m.RewardToken) - i = encodeVarintMsgs(dAtA, i, uint64(len(m.RewardToken))) + if len(m.RewardDenom) > 0 { + i -= len(m.RewardDenom) + copy(dAtA[i:], m.RewardDenom) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.RewardDenom))) i-- dAtA[i] = 0x32 } @@ -3559,7 +3574,11 @@ func (m *MsgValsetUpdatedClaim) Size() (n int) { } l = m.RewardAmount.Size() n += 1 + l + sovMsgs(uint64(l)) - l = len(m.RewardToken) + l = len(m.RewardDenom) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + l = len(m.RewardRecipient) if l > 0 { n += 1 + l + sovMsgs(uint64(l)) } @@ -6156,7 +6175,7 @@ func (m *MsgValsetUpdatedClaim) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RewardToken", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RewardDenom", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6184,9 +6203,41 @@ func (m *MsgValsetUpdatedClaim) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RewardToken = string(dAtA[iNdEx:postIndex]) + m.RewardDenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RewardRecipient", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RewardRecipient = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Orchestrator", wireType) } diff --git a/module/x/gravity/types/types.pb.go b/module/x/gravity/types/types.pb.go index e25dcd605..96c87c58f 100644 --- a/module/x/gravity/types/types.pb.go +++ b/module/x/gravity/types/types.pb.go @@ -89,7 +89,7 @@ type Valset struct { Height uint64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` RewardAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=reward_amount,json=rewardAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"reward_amount"` // the reward token in it's Ethereum hex address representation - RewardToken string `protobuf:"bytes,5,opt,name=reward_token,json=rewardToken,proto3" json:"reward_token,omitempty"` + RewardDenom string `protobuf:"bytes,5,opt,name=reward_denom,json=rewardDenom,proto3" json:"reward_denom,omitempty"` } func (m *Valset) Reset() { *m = Valset{} } @@ -146,9 +146,9 @@ func (m *Valset) GetHeight() uint64 { return 0 } -func (m *Valset) GetRewardToken() string { +func (m *Valset) GetRewardDenom() string { if m != nil { - return m.RewardToken + return m.RewardDenom } return "" } @@ -413,49 +413,49 @@ func init() { func init() { proto.RegisterFile("gravity/v1/types.proto", fileDescriptor_163831c23fcc179f) } var fileDescriptor_163831c23fcc179f = []byte{ - // 660 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xbf, 0x6f, 0xd3, 0x40, - 0x14, 0x8e, 0x9b, 0xf4, 0xd7, 0x25, 0xa8, 0xe0, 0x96, 0x2a, 0xa2, 0xc2, 0x09, 0x19, 0x50, 0x18, - 0x6a, 0x37, 0x61, 0x2b, 0x03, 0x6a, 0x4a, 0x25, 0x2a, 0xf1, 0x4b, 0xa6, 0x74, 0x60, 0xb1, 0xce, - 0xf6, 0x93, 0x73, 0x8a, 0xed, 0xb3, 0xee, 0x2e, 0x29, 0x9d, 0x98, 0x90, 0x18, 0x19, 0x19, 0xbb, - 0xf1, 0x37, 0xf0, 0x1f, 0x74, 0xec, 0x88, 0x18, 0x2a, 0xd4, 0x2e, 0x48, 0xfc, 0x13, 0xe8, 0x7e, - 0x38, 0xa4, 0x65, 0xec, 0x64, 0x7f, 0xdf, 0xdd, 0xfb, 0xde, 0xf7, 0x9e, 0xdf, 0x33, 0x5a, 0x4f, - 0x18, 0x9e, 0x10, 0x71, 0xec, 0x4d, 0x7a, 0x9e, 0x38, 0x2e, 0x80, 0xbb, 0x05, 0xa3, 0x82, 0xda, - 0xc8, 0xf0, 0xee, 0xa4, 0x77, 0xcf, 0x89, 0x28, 0xcf, 0x28, 0xf7, 0x42, 0xcc, 0xc1, 0x9b, 0xf4, - 0x42, 0x10, 0xb8, 0xe7, 0x45, 0x94, 0xe4, 0xfa, 0xee, 0xcc, 0x79, 0x3e, 0x9a, 0x9e, 0x4b, 0x60, - 0xce, 0xd7, 0x12, 0x9a, 0x50, 0xf5, 0xea, 0xc9, 0x37, 0xcd, 0x76, 0x7c, 0xb4, 0x32, 0x60, 0x24, - 0x4e, 0xe0, 0x10, 0xa7, 0x24, 0xc6, 0x82, 0x32, 0x7b, 0x0d, 0xcd, 0x17, 0xf4, 0x08, 0x58, 0xd3, - 0x6a, 0x5b, 0xdd, 0x9a, 0xaf, 0x81, 0xfd, 0x08, 0xdd, 0x06, 0x31, 0x04, 0x06, 0xe3, 0x2c, 0xc0, - 0x71, 0xcc, 0x80, 0xf3, 0xe6, 0x5c, 0xdb, 0xea, 0x2e, 0xfb, 0x2b, 0x25, 0xbf, 0xa3, 0xe9, 0xce, - 0x1f, 0x0b, 0x2d, 0x1c, 0xe2, 0x94, 0x83, 0x90, 0x5a, 0x39, 0xcd, 0x23, 0x28, 0xb5, 0x14, 0xb0, - 0x9f, 0xa0, 0xc5, 0x0c, 0xb2, 0x10, 0x98, 0x94, 0xa8, 0x76, 0xeb, 0xfd, 0x0d, 0xf7, 0x5f, 0xa1, - 0xee, 0x35, 0x3f, 0x83, 0xda, 0xe9, 0x79, 0xab, 0xe2, 0x97, 0x11, 0xf6, 0x3a, 0x5a, 0x18, 0x02, - 0x49, 0x86, 0xa2, 0x59, 0x55, 0x9a, 0x06, 0xd9, 0x6f, 0xd1, 0x2d, 0x06, 0x47, 0x98, 0xc5, 0x01, - 0xce, 0xe8, 0x38, 0x17, 0xcd, 0x9a, 0x74, 0x37, 0x70, 0x65, 0xf4, 0xcf, 0xf3, 0xd6, 0xc3, 0x84, - 0x88, 0xe1, 0x38, 0x74, 0x23, 0x9a, 0x79, 0xa6, 0x53, 0xfa, 0xb1, 0xc9, 0xe3, 0x91, 0x69, 0xfa, - 0x7e, 0x2e, 0xfc, 0x86, 0x16, 0xd9, 0x51, 0x1a, 0xf6, 0x03, 0x64, 0x70, 0x20, 0xe8, 0x08, 0xf2, - 0xe6, 0xbc, 0xaa, 0xb8, 0xae, 0xb9, 0x03, 0x49, 0x75, 0x3e, 0x59, 0xa8, 0xf5, 0x02, 0x73, 0xf1, - 0x3a, 0xe4, 0xc0, 0x26, 0x10, 0xef, 0x99, 0x6e, 0x0c, 0x52, 0x1a, 0x8d, 0x9e, 0x6b, 0x6f, 0x2e, - 0x5a, 0xd5, 0xc9, 0x82, 0x50, 0xb2, 0x81, 0x29, 0x40, 0x37, 0xe5, 0x8e, 0x3e, 0x9a, 0xbd, 0xdf, - 0x47, 0x77, 0xa7, 0xcd, 0xbe, 0x12, 0x31, 0xa7, 0x22, 0x56, 0xe1, 0xff, 0x1c, 0x9d, 0x6d, 0xd4, - 0xd8, 0xf3, 0x77, 0xfb, 0x5b, 0x07, 0xf4, 0x19, 0xe4, 0x34, 0x93, 0xad, 0x07, 0x16, 0xf5, 0xb7, - 0x54, 0x96, 0x65, 0x5f, 0x03, 0xc9, 0xc6, 0xf2, 0xd8, 0x7c, 0x3b, 0x0d, 0x3a, 0x1f, 0xd1, 0xda, - 0xbb, 0x7c, 0x88, 0x53, 0xa1, 0x7b, 0xff, 0x86, 0xd1, 0x82, 0x72, 0x9c, 0xca, 0xdb, 0x82, 0x88, - 0x14, 0x4a, 0x0d, 0x05, 0xec, 0x36, 0xaa, 0xc7, 0xc0, 0x23, 0x46, 0x0a, 0x41, 0x68, 0x6e, 0x94, - 0x66, 0x29, 0xd9, 0x36, 0x81, 0x59, 0x02, 0x22, 0xd0, 0x5f, 0xbf, 0xa6, 0x6c, 0xd7, 0x35, 0xf7, - 0x4a, 0x52, 0xdb, 0x8d, 0xcf, 0x27, 0xad, 0xca, 0xd7, 0x93, 0x56, 0xe5, 0xf7, 0x49, 0xcb, 0xea, - 0x7c, 0xb3, 0xd0, 0xca, 0x0e, 0x61, 0x31, 0xa3, 0xc5, 0x8d, 0x93, 0x4f, 0x4b, 0xac, 0xce, 0x94, - 0x68, 0x3b, 0x08, 0x31, 0x88, 0x48, 0x41, 0x20, 0x17, 0x5c, 0x19, 0x6a, 0xf8, 0x33, 0x8c, 0xdd, - 0x44, 0x8b, 0x7a, 0x6e, 0x78, 0x73, 0xbe, 0x5d, 0xed, 0xd6, 0xfc, 0x12, 0x5e, 0x73, 0xfa, 0xdd, - 0x42, 0xab, 0xfb, 0x83, 0xdd, 0x97, 0x20, 0x70, 0x8c, 0x05, 0xbe, 0xb1, 0xdb, 0xa7, 0x68, 0x29, - 0x33, 0x5a, 0xca, 0x70, 0xbd, 0x7f, 0xdf, 0xd5, 0x03, 0xe1, 0xaa, 0xe5, 0x35, 0x9b, 0xec, 0x96, - 0x09, 0xcd, 0x3a, 0x4c, 0x83, 0xec, 0x0d, 0xb4, 0x4c, 0xc2, 0x28, 0xd0, 0x25, 0xab, 0x99, 0xf7, - 0x97, 0x48, 0x18, 0xa9, 0x21, 0xb8, 0xe2, 0xbd, 0x32, 0x08, 0x4e, 0x2f, 0x1c, 0xeb, 0xec, 0xc2, - 0xb1, 0x7e, 0x5d, 0x38, 0xd6, 0x97, 0x4b, 0xa7, 0x72, 0x76, 0xe9, 0x54, 0x7e, 0x5c, 0x3a, 0x95, - 0xf7, 0x7b, 0x33, 0xdb, 0x41, 0x73, 0x9a, 0x1d, 0xab, 0xbf, 0x43, 0x44, 0xd3, 0x72, 0x49, 0xcc, - 0x7e, 0x6e, 0x86, 0x6a, 0x40, 0xbc, 0x8c, 0xc6, 0xe3, 0x14, 0xbc, 0x0f, 0x5e, 0xf9, 0xe3, 0x52, - 0x0b, 0x14, 0x2e, 0xa8, 0xb0, 0xc7, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x29, 0x3e, 0xe9, - 0xd0, 0x04, 0x00, 0x00, + // 658 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x3d, 0x6f, 0xd3, 0x5c, + 0x14, 0x8e, 0x9b, 0xf4, 0xeb, 0x26, 0xaf, 0xfa, 0xe2, 0x96, 0xca, 0xa2, 0xc2, 0x09, 0x19, 0x50, + 0x18, 0x6a, 0x37, 0x61, 0x2b, 0x03, 0x6a, 0x4a, 0x25, 0x2a, 0xf1, 0x25, 0x03, 0x1d, 0x58, 0xac, + 0x6b, 0xfb, 0xc8, 0xb9, 0xaa, 0xed, 0x6b, 0xdd, 0x7b, 0x93, 0xd2, 0x89, 0x09, 0x89, 0x91, 0x91, + 0xb1, 0x1b, 0xbf, 0x81, 0x7f, 0xd0, 0xb1, 0x23, 0x62, 0xa8, 0x50, 0xbb, 0x20, 0xf1, 0x27, 0xd0, + 0xfd, 0x70, 0x48, 0xcb, 0xd8, 0xc9, 0x7e, 0x9e, 0x7b, 0xcf, 0x73, 0x9e, 0x73, 0x7c, 0x8e, 0xd1, + 0x7a, 0xca, 0xf0, 0x84, 0x88, 0x63, 0x7f, 0xd2, 0xf7, 0xc5, 0x71, 0x09, 0xdc, 0x2b, 0x19, 0x15, + 0xd4, 0x46, 0x86, 0xf7, 0x26, 0xfd, 0x3b, 0x6e, 0x4c, 0x79, 0x4e, 0xb9, 0x1f, 0x61, 0x0e, 0xfe, + 0xa4, 0x1f, 0x81, 0xc0, 0x7d, 0x3f, 0xa6, 0xa4, 0xd0, 0x77, 0x67, 0xce, 0x8b, 0xc3, 0xe9, 0xb9, + 0x04, 0xe6, 0x7c, 0x2d, 0xa5, 0x29, 0x55, 0xaf, 0xbe, 0x7c, 0xd3, 0x6c, 0x37, 0x40, 0x2b, 0x43, + 0x46, 0x92, 0x14, 0x0e, 0x70, 0x46, 0x12, 0x2c, 0x28, 0xb3, 0xd7, 0xd0, 0x7c, 0x49, 0x8f, 0x80, + 0x39, 0x56, 0xc7, 0xea, 0x35, 0x02, 0x0d, 0xec, 0x07, 0xe8, 0x7f, 0x10, 0x23, 0x60, 0x30, 0xce, + 0x43, 0x9c, 0x24, 0x0c, 0x38, 0x77, 0xe6, 0x3a, 0x56, 0x6f, 0x39, 0x58, 0xa9, 0xf8, 0x1d, 0x4d, + 0x77, 0x7f, 0x5b, 0x68, 0xe1, 0x00, 0x67, 0x1c, 0x84, 0xd4, 0x2a, 0x68, 0x11, 0x43, 0xa5, 0xa5, + 0x80, 0xfd, 0x08, 0x2d, 0xe6, 0x90, 0x47, 0xc0, 0xa4, 0x44, 0xbd, 0xd7, 0x1c, 0x6c, 0x78, 0x7f, + 0x0b, 0xf5, 0xae, 0xf9, 0x19, 0x36, 0x4e, 0xcf, 0xdb, 0xb5, 0xa0, 0x8a, 0xb0, 0xd7, 0xd1, 0xc2, + 0x08, 0x48, 0x3a, 0x12, 0x4e, 0x5d, 0x69, 0x1a, 0x64, 0xbf, 0x46, 0xff, 0x31, 0x38, 0xc2, 0x2c, + 0x09, 0x71, 0x4e, 0xc7, 0x85, 0x70, 0x1a, 0xd2, 0xdd, 0xd0, 0x93, 0xd1, 0x3f, 0xce, 0xdb, 0xf7, + 0x53, 0x22, 0x46, 0xe3, 0xc8, 0x8b, 0x69, 0xee, 0x9b, 0x4e, 0xe9, 0xc7, 0x26, 0x4f, 0x0e, 0x4d, + 0xd3, 0xf7, 0x0b, 0x11, 0xb4, 0xb4, 0xc8, 0x8e, 0xd2, 0xb0, 0xef, 0x21, 0x83, 0xc3, 0x04, 0x0a, + 0x9a, 0x3b, 0xf3, 0xaa, 0xe2, 0xa6, 0xe6, 0x9e, 0x48, 0xaa, 0xfb, 0xd1, 0x42, 0xed, 0x67, 0x98, + 0x8b, 0x97, 0x11, 0x07, 0x36, 0x81, 0x64, 0xcf, 0x74, 0x63, 0x98, 0xd1, 0xf8, 0xf0, 0xa9, 0xf6, + 0xe6, 0xa1, 0x55, 0x9d, 0x2c, 0x8c, 0x24, 0x1b, 0x9a, 0x02, 0x74, 0x53, 0x6e, 0xe9, 0xa3, 0xd9, + 0xfb, 0x03, 0x74, 0x7b, 0xda, 0xec, 0x2b, 0x11, 0x73, 0x2a, 0x62, 0x15, 0xfe, 0xcd, 0xd1, 0xdd, + 0x46, 0xad, 0xbd, 0x60, 0x77, 0xb0, 0xf5, 0x86, 0x2a, 0x5f, 0xb2, 0xf5, 0xc0, 0xe2, 0xc1, 0x96, + 0xca, 0xb2, 0x1c, 0x68, 0x20, 0x59, 0x5d, 0x89, 0xfe, 0x76, 0x1a, 0x74, 0x3f, 0xa0, 0xb5, 0xb7, + 0xc5, 0x08, 0x67, 0x42, 0xf7, 0xfe, 0x15, 0xa3, 0x25, 0xe5, 0x38, 0x93, 0xb7, 0x05, 0x11, 0x19, + 0x54, 0x1a, 0x0a, 0xd8, 0x1d, 0xd4, 0x4c, 0x80, 0xc7, 0x8c, 0x94, 0x82, 0xd0, 0xc2, 0x28, 0xcd, + 0x52, 0xb2, 0x6d, 0x02, 0xb3, 0x14, 0x44, 0xa8, 0xbf, 0x7e, 0x43, 0xd9, 0x6e, 0x6a, 0xee, 0x85, + 0xa4, 0xb6, 0x5b, 0x9f, 0x4e, 0xda, 0xb5, 0x2f, 0x27, 0xed, 0xda, 0xaf, 0x93, 0xb6, 0xd5, 0xfd, + 0x6a, 0xa1, 0x95, 0x1d, 0xc2, 0x12, 0x46, 0xcb, 0x1b, 0x27, 0x9f, 0x96, 0x58, 0x9f, 0x29, 0xd1, + 0x76, 0x11, 0x62, 0x10, 0x93, 0x92, 0x40, 0x21, 0xb8, 0x32, 0xd4, 0x0a, 0x66, 0x18, 0xdb, 0x41, + 0x8b, 0x7a, 0x6e, 0xb8, 0x33, 0xdf, 0xa9, 0xf7, 0x1a, 0x41, 0x05, 0xaf, 0x39, 0xfd, 0x66, 0xa1, + 0xd5, 0xfd, 0xe1, 0xee, 0x73, 0x10, 0x38, 0xc1, 0x02, 0xdf, 0xd8, 0xed, 0x63, 0xb4, 0x94, 0x1b, + 0x2d, 0x65, 0xb8, 0x39, 0xb8, 0xeb, 0xe9, 0x81, 0xf0, 0xd4, 0xf2, 0x9a, 0x4d, 0xf6, 0xaa, 0x84, + 0x66, 0x1d, 0xa6, 0x41, 0xf6, 0x06, 0x5a, 0x26, 0x51, 0x6c, 0xe6, 0x53, 0xcd, 0x7c, 0xb0, 0x44, + 0xa2, 0x58, 0x0d, 0xc1, 0x15, 0xef, 0xb5, 0x61, 0x78, 0x7a, 0xe1, 0x5a, 0x67, 0x17, 0xae, 0xf5, + 0xf3, 0xc2, 0xb5, 0x3e, 0x5f, 0xba, 0xb5, 0xb3, 0x4b, 0xb7, 0xf6, 0xfd, 0xd2, 0xad, 0xbd, 0xdb, + 0x9b, 0xd9, 0x0e, 0x5a, 0xd0, 0xfc, 0x58, 0xfd, 0x1d, 0x62, 0x9a, 0x55, 0x4b, 0x62, 0xf6, 0x73, + 0x33, 0x52, 0x03, 0xe2, 0xe7, 0x34, 0x19, 0x67, 0xe0, 0xbf, 0xf7, 0xab, 0x1f, 0x97, 0x5a, 0xa0, + 0x68, 0x41, 0x85, 0x3d, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x36, 0x64, 0xd2, 0x36, 0xd0, 0x04, + 0x00, 0x00, } func (this *UnhaltBridgeProposal) Equal(that interface{}) bool { @@ -584,10 +584,10 @@ func (m *Valset) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.RewardToken) > 0 { - i -= len(m.RewardToken) - copy(dAtA[i:], m.RewardToken) - i = encodeVarintTypes(dAtA, i, uint64(len(m.RewardToken))) + if len(m.RewardDenom) > 0 { + i -= len(m.RewardDenom) + copy(dAtA[i:], m.RewardDenom) + i = encodeVarintTypes(dAtA, i, uint64(len(m.RewardDenom))) i-- dAtA[i] = 0x2a } @@ -910,7 +910,7 @@ func (m *Valset) Size() (n int) { } l = m.RewardAmount.Size() n += 1 + l + sovTypes(uint64(l)) - l = len(m.RewardToken) + l = len(m.RewardDenom) if l > 0 { n += 1 + l + sovTypes(uint64(l)) } @@ -1268,7 +1268,7 @@ func (m *Valset) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RewardToken", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RewardDenom", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1296,7 +1296,7 @@ func (m *Valset) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RewardToken = string(dAtA[iNdEx:postIndex]) + m.RewardDenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/module/x/gravity/types/types_test.go b/module/x/gravity/types/types_test.go index b3dd40a52..b8cddc9f2 100644 --- a/module/x/gravity/types/types_test.go +++ b/module/x/gravity/types/types_test.go @@ -3,6 +3,7 @@ package types import ( "bytes" "encoding/hex" + "fmt" mrand "math/rand" "testing" @@ -13,13 +14,21 @@ import ( ) func TestValsetConfirmHash(t *testing.T) { - powers := []uint64{3333, 3333, 3333} - ethAddresses := []string{ - "0xc783df8a850f42e7F7e57013759C285caa701eB6", - "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4", - "0xE5904695748fe4A84b40b3fc79De2277660BD1D3", - } - members := make(InternalBridgeValidators, len(powers)) + const ( + gravityID = "foo" + denom = "foo_denom" + ) + var ( + reward = sdk.NewInt(1000) + powers = []uint64{3333, 3333, 3333} + ethAddresses = []string{ + "0xc783df8a850f42e7F7e57013759C285caa701eB6", + "0xE5904695748fe4A84b40b3fc79De2277660BD1D3", + "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4", + } + members = make(InternalBridgeValidators, len(powers)) + ) + for i := range powers { bv := BridgeValidator{ Power: powers[i], @@ -30,15 +39,43 @@ func TestValsetConfirmHash(t *testing.T) { members[i] = ibv } - v, err := NewValset(0, 0, members, sdk.NewInt(0), ZeroAddress()) + // check with filled reward + + v, err := NewValset(0, 0, members, reward, denom) + + fmt.Printf("%+v", v) + require.NoError(t, err) - // normally we would load the GravityID from the store, but for this test we use - // the same hardcoded value in the solidity tests - hash := v.GetCheckpoint("foo") + hash := v.GetCheckpoint(gravityID) hexHash := hex.EncodeToString(hash) - correctHash := "0xaca2f283f21a03ba182dc7d34a55c04771b25087401d680011df7dcba453f798"[2:] + // you can find the same value in the orchestrator tests, so if you update it update there as well + correctHash := "0x2751e9f1cdef7c6f1365e81a42707c0ecff75e6cd7cecd6c456e571234548a1e"[2:] assert.Equal(t, correctHash, hexHash) + + // check with empty reward + + v, err = NewValset(0, 0, members, sdk.ZeroInt(), "") + require.NoError(t, err) + + hash = v.GetCheckpoint(gravityID) + hexHash = hex.EncodeToString(hash) + // you can find the same value in the orchestrator tests, so if you update it update there as well + correctHash = "0xa2c8dc58c06fa959763bffd4c8fe8668869b7b5c866a7b0f0f1739b92a6cd5d1"[2:] + assert.Equal(t, correctHash, hexHash) + + // check with invalid nonce and nonce reward + + v, err = NewValset(1, 0, members, sdk.ZeroInt(), "") + require.NoError(t, err) + + hash = v.GetCheckpoint(gravityID) + hexHash = hex.EncodeToString(hash) + // you can find the same value in the orchestrator tests, so if you update it update there as well + correctHash = "0xa2c8dc58c06fa959763bffd4c8fe8668869b7b5c866a7b0f0f1739b92a6cd5d1"[2:] + // the nonce is not 0 hence the hash is invalid + assert.NotEqual(t, correctHash, hexHash) + } func TestValsetCheckpointGold1(t *testing.T) { @@ -47,7 +84,7 @@ func TestValsetCheckpointGold1(t *testing.T) { EthereumAddress: "0xc783df8a850f42e7F7e57013759C285caa701eB6", }}.ToInternal() require.NoError(t, err) - src, err := NewValset(0, 0, *bridgeValidators, sdk.NewInt(0), ZeroAddress()) + src, err := NewValset(0, 0, *bridgeValidators, sdk.NewInt(0), "") require.NoError(t, err) // normally we would load the GravityID from the store, but for this test we use @@ -55,7 +92,7 @@ func TestValsetCheckpointGold1(t *testing.T) { ourHash := src.GetCheckpoint("foo") // hash from bridge contract - goldHash := "0x89731c26bab12cf0cb5363ef9abab6f9bd5496cf758a2309311c7946d54bca85"[2:] + goldHash := "0xe3d534594d4a3cf357de3b07a7b26dbc31daab10edb881cb3eef0292cf0669c0"[2:] assert.Equal(t, goldHash, hex.EncodeToString(ourHash)) } @@ -129,19 +166,7 @@ func TestValsetSort(t *testing.T) { src BridgeValidators exp BridgeValidators }{ - "by power desc": { - src: BridgeValidators{ - {Power: 1, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(3)}, 20)).String()}, - {Power: 2, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(1)}, 20)).String()}, - {Power: 3, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(2)}, 20)).String()}, - }, - exp: BridgeValidators{ - {Power: 3, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(2)}, 20)).String()}, - {Power: 2, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(1)}, 20)).String()}, - {Power: 1, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(3)}, 20)).String()}, - }, - }, - "by eth addr on same power": { + "by eth addres": { src: BridgeValidators{ {Power: 1, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(2)}, 20)).String()}, {Power: 1, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(1)}, 20)).String()}, @@ -153,29 +178,27 @@ func TestValsetSort(t *testing.T) { {Power: 1, EthereumAddress: gethcommon.BytesToAddress(bytes.Repeat([]byte{byte(3)}, 20)).String()}, }, }, - // if you're thinking about changing this due to a change in the sorting algorithm - // you MUST go change this in gravity_utils/types.rs as well. You will also break all - // bridges in production when they try to migrate so use extreme caution! + // we ignore the power and sort by the address only "real world": { src: BridgeValidators{ - {Power: 678509841, EthereumAddress: "0x6db48cBBCeD754bDc760720e38E456144e83269b"}, - {Power: 671724742, EthereumAddress: "0x8E91960d704Df3fF24ECAb78AB9df1B5D9144140"}, - {Power: 685294939, EthereumAddress: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D"}, + {Power: 617443955, EthereumAddress: "0x3511A211A6759d48d107898302042d1301187BA9"}, {Power: 671724742, EthereumAddress: "0x0A7254b318dd742A3086882321C27779B4B642a6"}, + {Power: 291759231, EthereumAddress: "0xa14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, + {Power: 291759231, EthereumAddress: "0xA24879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, + {Power: 291759231, EthereumAddress: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, {Power: 671724742, EthereumAddress: "0x454330deAaB759468065d08F2b3B0562caBe1dD1"}, - {Power: 617443955, EthereumAddress: "0x3511A211A6759d48d107898302042d1301187BA9"}, {Power: 6785098, EthereumAddress: "0x37A0603dA2ff6377E5C7f75698dabA8EE4Ba97B8"}, - {Power: 291759231, EthereumAddress: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, + {Power: 685294939, EthereumAddress: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D"}, }, exp: BridgeValidators{ - {Power: 685294939, EthereumAddress: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D"}, - {Power: 678509841, EthereumAddress: "0x6db48cBBCeD754bDc760720e38E456144e83269b"}, {Power: 671724742, EthereumAddress: "0x0A7254b318dd742A3086882321C27779B4B642a6"}, - {Power: 671724742, EthereumAddress: "0x454330deAaB759468065d08F2b3B0562caBe1dD1"}, - {Power: 671724742, EthereumAddress: "0x8E91960d704Df3fF24ECAb78AB9df1B5D9144140"}, {Power: 617443955, EthereumAddress: "0x3511A211A6759d48d107898302042d1301187BA9"}, - {Power: 291759231, EthereumAddress: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, {Power: 6785098, EthereumAddress: "0x37A0603dA2ff6377E5C7f75698dabA8EE4Ba97B8"}, + {Power: 671724742, EthereumAddress: "0x454330deAaB759468065d08F2b3B0562caBe1dD1"}, + {Power: 685294939, EthereumAddress: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D"}, + {Power: 291759231, EthereumAddress: "0xa14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, + {Power: 291759231, EthereumAddress: "0xA24879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, + {Power: 291759231, EthereumAddress: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326"}, }, }, } diff --git a/module/x/gravity/types/validation.go b/module/x/gravity/types/validation.go index d9efae6ea..cab49dbba 100644 --- a/module/x/gravity/types/validation.go +++ b/module/x/gravity/types/validation.go @@ -2,14 +2,13 @@ package types import ( "fmt" - math "math" + "math" "math/big" "sort" "strings" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -75,23 +74,19 @@ func (i InternalBridgeValidator) ToExternal() BridgeValidator { // InternalBridgeValidators is the sorted set of validator data for Ethereum bridge MultiSig set type InternalBridgeValidators []*InternalBridgeValidator -func (i InternalBridgeValidators) ToExternal() BridgeValidators { - bridgeValidators := make([]BridgeValidator, len(i)) +func (vals InternalBridgeValidators) ToExternal() BridgeValidators { + bridgeValidators := make([]BridgeValidator, len(vals)) for b := range bridgeValidators { - bridgeValidators[b] = i[b].ToExternal() + bridgeValidators[b] = vals[b].ToExternal() } - return BridgeValidators(bridgeValidators) + return bridgeValidators } -// Sort sorts the validators by power -func (b InternalBridgeValidators) Sort() { - sort.Slice(b, func(i, j int) bool { - if b[i].Power == b[j].Power { - // Secondary sort on eth address in case powers are equal - return EthAddrLessThan(b[i].EthereumAddress, b[j].EthereumAddress) - } - return b[i].Power > b[j].Power +// Sort sorts the validators by eth address asc. +func (vals InternalBridgeValidators) Sort() { + sort.Slice(vals, func(j, k int) bool { + return strings.ToLower(vals[j].EthereumAddress.GetAddress()) < strings.ToLower(vals[k].EthereumAddress.GetAddress()) }) } @@ -109,10 +104,10 @@ func (b InternalBridgeValidators) Sort() { // if the total on chain voting power increases by 1% due to inflation, we shouldn't have to generate a new validator // set, after all the validators retained their relative percentages during inflation and normalized Gravity bridge power // shows no difference. -func (b InternalBridgeValidators) PowerDiff(c InternalBridgeValidators) float64 { +func (vals InternalBridgeValidators) PowerDiff(c InternalBridgeValidators) float64 { powers := map[string]int64{} - // loop over b and initialize the map with their powers - for _, bv := range b { + // loop over vals and initialize the map with their powers + for _, bv := range vals { powers[bv.EthereumAddress.GetAddress()] = int64(bv.Power) } @@ -136,44 +131,44 @@ func (b InternalBridgeValidators) PowerDiff(c InternalBridgeValidators) float64 } // TotalPower returns the total power in the bridge validator set -func (b InternalBridgeValidators) TotalPower() (out uint64) { - for _, v := range b { +func (vals InternalBridgeValidators) TotalPower() (out uint64) { + for _, v := range vals { out += v.Power } return } // HasDuplicates returns true if there are duplicates in the set -func (b InternalBridgeValidators) HasDuplicates() bool { - m := make(map[string]struct{}, len(b)) +func (vals InternalBridgeValidators) HasDuplicates() bool { + m := make(map[string]struct{}, len(vals)) // creates a hashmap then ensures that the hashmap and the array // have the same length, this acts as an O(n) duplicates check - for i := range b { - m[b[i].EthereumAddress.GetAddress()] = struct{}{} + for i := range vals { + m[vals[i].EthereumAddress.GetAddress()] = struct{}{} } - return len(m) != len(b) + return len(m) != len(vals) } // GetPowers returns only the power values for all members -func (b InternalBridgeValidators) GetPowers() []uint64 { - r := make([]uint64, len(b)) - for i := range b { - r[i] = b[i].Power +func (vals InternalBridgeValidators) GetPowers() []uint64 { + r := make([]uint64, len(vals)) + for i := range vals { + r[i] = vals[i].Power } return r } // ValidateBasic performs stateless checks -func (b InternalBridgeValidators) ValidateBasic() error { - if len(b) == 0 { +func (vals InternalBridgeValidators) ValidateBasic() error { + if len(vals) == 0 { return ErrEmpty } - for i := range b { - if err := b[i].ValidateBasic(); err != nil { + for i := range vals { + if err := vals[i].ValidateBasic(); err != nil { return sdkerrors.Wrapf(err, "member %d", i) } } - if b.HasDuplicates() { + if vals.HasDuplicates() { return sdkerrors.Wrap(ErrDuplicate, "addresses") } return nil @@ -184,7 +179,7 @@ func (b InternalBridgeValidators) ValidateBasic() error { ////////////////////////////////////// // NewValset returns a new valset -func NewValset(nonce, height uint64, members InternalBridgeValidators, rewardAmount sdk.Int, rewardToken EthAddress) (*Valset, error) { +func NewValset(nonce, height uint64, members InternalBridgeValidators, rewardAmount sdk.Int, rewardDenom string) (*Valset, error) { if err := members.ValidateBasic(); err != nil { return nil, sdkerrors.Wrap(err, "invalid members") } @@ -193,20 +188,12 @@ func NewValset(nonce, height uint64, members InternalBridgeValidators, rewardAmo for _, val := range members { mem = append(mem, val.ToExternal()) } - vs := Valset{Nonce: uint64(nonce), Members: mem, Height: height, RewardAmount: rewardAmount, RewardToken: rewardToken.GetAddress()} - return &vs, - nil + vs := Valset{Nonce: nonce, Members: mem, Height: height, RewardAmount: rewardAmount, RewardDenom: rewardDenom} + return &vs, nil } // GetCheckpoint returns the checkpoint func (v Valset) GetCheckpoint(gravityIDstring string) []byte { - - // error case here should not occur outside of testing since the above is a constant - contractAbi, abiErr := abi.JSON(strings.NewReader(ValsetCheckpointABIJSON)) - if abiErr != nil { - panic("Bad ABI constant!") - } - // the contract argument is not a arbitrary length array but a fixed length 32 byte // array, therefore we have to utf8 encode the string (the default in this case) and // then copy the variable length encoded data into a fixed length array. This function @@ -216,13 +203,6 @@ func (v Valset) GetCheckpoint(gravityIDstring string) []byte { panic(err) } - // this should never happen, unless an invalid paramater value has been set by the chain - err = ValidateEthAddress(v.RewardToken) - if err != nil { - panic(err) - } - rewardToken := gethcommon.HexToAddress(v.RewardToken) - if v.RewardAmount.BigInt() == nil { // this must be programmer error panic("Invalid reward amount passed in valset GetCheckpoint!") @@ -242,7 +222,7 @@ func (v Valset) GetCheckpoint(gravityIDstring string) []byte { // the word 'checkpoint' needs to be the same as the 'name' above in the checkpointAbiJson // but other than that it's a constant that has no impact on the output. This is because // it gets encoded as a function name which we must then discard. - bytes, packErr := contractAbi.Pack("checkpoint", gravityID, checkpoint, big.NewInt(int64(v.Nonce)), memberAddresses, convertedPowers, rewardAmount, rewardToken) + bytes, packErr := ValsetCheckpointABI.Pack("checkpoint", gravityID, checkpoint, big.NewInt(int64(v.Nonce)), memberAddresses, convertedPowers, rewardAmount, v.RewardDenom) // this should never happen outside of test since any case that could crash on encoding // should be filtered above. @@ -267,7 +247,7 @@ func (v *Valset) WithoutEmptyMembers() *Valset { Members: make([]BridgeValidator, 0, len(v.Members)), Height: 0, RewardAmount: sdk.Int{}, - RewardToken: "", + RewardDenom: "", } for i := range v.Members { if _, err := v.Members[i].ToInternal(); err == nil { diff --git a/orchestrator/Cargo.lock b/orchestrator/Cargo.lock index 9a810f255..165d1038c 100644 --- a/orchestrator/Cargo.lock +++ b/orchestrator/Cargo.lock @@ -186,7 +186,7 @@ dependencies = [ [[package]] name = "clarity" version = "0.5.1" -source = "git+https://github.com/onomyprotocol/clarity.git?rev=52bc13f7d2778d293e2d787528d481604620a281#52bc13f7d2778d293e2d787528d481604620a281" +source = "git+https://github.com/onomyprotocol/clarity.git?rev=0b69d4aabae546a2d1bd296f04ba64a8a1546b9a#0b69d4aabae546a2d1bd296f04ba64a8a1546b9a" dependencies = [ "secp256k1", "serde", @@ -2239,7 +2239,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "web30" version = "0.18.3" -source = "git+https://github.com/onomyprotocol/web30.git?rev=645c0509246fedff01aed1aa4c26eb804f3fec1a#645c0509246fedff01aed1aa4c26eb804f3fec1a" +source = "git+https://github.com/onomyprotocol/web30.git?rev=3e009bf55c7740ef5bb692c1f1ec8d9724687dbc#3e009bf55c7740ef5bb692c1f1ec8d9724687dbc" dependencies = [ "clarity", "hyper", diff --git a/orchestrator/cosmos_gravity/src/send.rs b/orchestrator/cosmos_gravity/src/send.rs index 500494195..26413a46f 100644 --- a/orchestrator/cosmos_gravity/src/send.rs +++ b/orchestrator/cosmos_gravity/src/send.rs @@ -13,9 +13,7 @@ use gravity_proto::{ }, }; use gravity_utils::{ - clarity::{ - constants::ZERO_ADDRESS, Address as EthAddress, PrivateKey as EthPrivateKey, Signature, - }, + clarity::{Address as EthAddress, PrivateKey as EthPrivateKey, Signature}, deep_space::{ address::Address, coin::Coin, error::CosmosGrpcError, private_key::PrivateKey, utils::bytes_to_hex_str, Contact, Msg, @@ -114,7 +112,7 @@ pub async fn send_valset_confirms( private_key, ) .await; - info!("Valset confirm res is {:?}", res); + debug!("Valset confirm res is {:?}", res); res } @@ -284,7 +282,8 @@ pub async fn send_ethereum_claims( block_height: valset.block_height.try_resize_to_u64().unwrap(), members: valset.members.iter().map(|v| v.into()).collect(), reward_amount: valset.reward_amount.to_string(), - reward_token: valset.reward_token.unwrap_or(ZERO_ADDRESS).to_string(), + reward_denom: valset.reward_denom, + reward_recipient: valset.reward_recipient, orchestrator: our_address.to_string(), }; let msg = Msg::new("/gravity.v1.MsgValsetUpdatedClaim", claim); diff --git a/orchestrator/ethereum_gravity/src/logic_call.rs b/orchestrator/ethereum_gravity/src/logic_call.rs index 69989a1b8..9c75ab21f 100644 --- a/orchestrator/ethereum_gravity/src/logic_call.rs +++ b/orchestrator/ethereum_gravity/src/logic_call.rs @@ -159,34 +159,6 @@ fn encode_logic_call_payload( fee_token_contracts.push(item.token_contract_address); } - // Solidity function signature - // function submitBatch( - // // The validators that approve the batch and new valset encoded as a ValsetArgs struct - // address[] memory _currentValidators, - // uint256[] memory _currentPowers, - // uint256 _currentValsetNonce, - // uint256 _rewardAmount, - // address _rewardToken, - // - // // These are arrays of the parts of the validators signatures - // Signature[] calldata _sigs, - // - // // The LogicCall arguments, encoded as a LogicCallArgs struct - // see the Ethereum ABI encoding documentation for the handling of structs as arguments - // as well as Gravity.sol for the struct definition. This call includes the primitive types - // of both. - // uint256[] transferAmounts; - // address[] transferTokenContracts; - // // The fees (transferred to msg.sender) - // uint256[] feeAmounts; - // address[] feeTokenContracts; - // // The arbitrary logic call - // address logicContractAddress; - // bytes payload; - // // Invalidation metadata - // uint256 timeOut; - // bytes32 invalidationId; - // uint256 invalidationNonce; let struct_tokens = &[ Token::Dynamic(transfer_amounts), transfer_token_contracts.into(), @@ -204,7 +176,7 @@ fn encode_logic_call_payload( Token::Struct(struct_tokens.to_vec()), ]; let payload = encode_call( - "submitLogicCall((address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[],(uint256[],address[],uint256[],address[],address,bytes,uint256,bytes32,uint256))", + "submitLogicCall((address[],uint256[],uint256,uint256,string),(uint8,bytes32,bytes32)[],(uint256[],address[],uint256[],address[],address,bytes,uint256,bytes32,uint256))", tokens, ) .unwrap(); @@ -224,7 +196,7 @@ mod tests { /// with a nontrivial struct in the header fn encode_abiv2_function_header() { // a golden master example encoding taken from Hardhat with all of it's parameters recreated - let encoded = "0x6941db9300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000aeeba3900000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b324da548f6070e8c8d78b205f139138e263d4bad21751e437a7ef31bc53928a803a5f8acc4b6662f839c0f60f5dbfb276957241b7b38feb360d3d7a0b32d63e20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000017c1736ccf692f653c433d7aa2ab45148c016f68000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000455e2bfa248696e76616c69646174696f6e49640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000038b86d9d8fafdd0a02ebd1a476432877b0107c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000038b86d9d8fafdd0a02ebd1a476432877b0107c8000000000000000000000000000000000000000000000000000000000000002074657374696e675061796c6f6164000000000000000000000000000000000000"; + let encoded = "0685c950000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000aeeba39000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b324da548f6070e8c8d78b205f139138e263d4bad21751e437a7ef31bc53928a803a5f8acc4b6662f839c0f60f5dbfb276957241b7b38feb360d3d7a0b32d63e20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000017c1736ccf692f653c433d7aa2ab45148c016f68000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000455e2bfa248696e76616c69646174696f6e49640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000038b86d9d8fafdd0a02ebd1a476432877b0107c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000038b86d9d8fafdd0a02ebd1a476432877b0107c8000000000000000000000000000000000000000000000000000000000000002074657374696e675061796c6f6164000000000000000000000000000000000000"; let encoded = hex_str_to_bytes(encoded).unwrap(); let token_contract_address = "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8" @@ -260,7 +232,7 @@ mod tests { // a validator set let valset = Valset { - reward_token: None, + reward_denom: "".to_string(), reward_amount: u256!(0), nonce: 0, members: vec![ValsetMember { diff --git a/orchestrator/ethereum_gravity/src/message_signatures.rs b/orchestrator/ethereum_gravity/src/message_signatures.rs index 43dbdd37e..a6e7ff877 100644 --- a/orchestrator/ethereum_gravity/src/message_signatures.rs +++ b/orchestrator/ethereum_gravity/src/message_signatures.rs @@ -1,7 +1,6 @@ use gravity_utils::{ clarity::{ abi::{encode_tokens, Token}, - constants::ZERO_ADDRESS, utils::get_ethereum_msg_hash, }, types::{LogicCall, TransactionBatch, Valset}, @@ -14,11 +13,8 @@ use gravity_utils::{ /// digest that is normally signed or may be used as a 'hash of the message' pub fn encode_valset_confirm(gravity_id: String, valset: &Valset) -> Vec { let (eth_addresses, powers) = valset.to_arrays(); - let reward_token = if let Some(v) = valset.reward_token { - v - } else { - ZERO_ADDRESS - }; + let reward_denom = valset.reward_denom.to_string(); + encode_tokens(&[ Token::FixedString(gravity_id), Token::FixedString("checkpoint".to_string()), @@ -26,7 +22,7 @@ pub fn encode_valset_confirm(gravity_id: String, valset: &Valset) -> Vec { eth_addresses.into(), powers.into(), valset.reward_amount.into(), - reward_token.into(), + Token::String(reward_denom), ]) } @@ -116,83 +112,83 @@ mod test { #[test] fn test_valset_signature() { - let correct_hash: Vec = - hex_str_to_bytes("0xaca2f283f21a03ba182dc7d34a55c04771b25087401d680011df7dcba453f798") - .unwrap(); + let gravity_id = "foo".to_string(); + let reward_amount = u256!(1000); + let reward_denom = "foo_denom".to_string(); - // a validator set, keep an eye on the sorting here as it's manually - // sorted and won't be identical to the other signer impl without manual - // ordering checks - let valset = Valset { + let mut members = vec![ + ValsetMember { + eth_address: "0xc783df8a850f42e7F7e57013759C285caa701eB6" + .parse() + .unwrap(), + + power: 3333, + }, + ValsetMember { + eth_address: "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4" + .parse() + .unwrap(), + + power: 3333, + }, + ValsetMember { + eth_address: "0xE5904695748fe4A84b40b3fc79De2277660BD1D3" + .parse() + .unwrap(), + + power: 3333, + }, + ]; + members.sort(); + + println!("members : {:?}", members.clone()); + + let mut valset = Valset { nonce: 0, - reward_amount: u256!(0), - reward_token: None, - members: vec![ - ValsetMember { - eth_address: "0xE5904695748fe4A84b40b3fc79De2277660BD1D3" - .parse() - .unwrap(), - - power: 3333, - }, - ValsetMember { - eth_address: "0xc783df8a850f42e7F7e57013759C285caa701eB6" - .parse() - .unwrap(), - - power: 3333, - }, - ValsetMember { - eth_address: "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4" - .parse() - .unwrap(), - - power: 3333, - }, - ], + reward_amount, + reward_denom, + members, }; - let checkpoint = encode_valset_confirm("foo".to_string(), &valset); + + // check with filled reward + + let checkpoint = encode_valset_confirm(gravity_id.clone(), &valset); let checkpoint_hash = Keccak256::digest(&checkpoint); + + let correct_hash: Vec = + hex_str_to_bytes("0x2751e9f1cdef7c6f1365e81a42707c0ecff75e6cd7cecd6c456e571234548a1e") + .unwrap(); assert_eq!( bytes_to_hex_str(&correct_hash), bytes_to_hex_str(&checkpoint_hash) ); - // the same valset, except with an intentionally incorrect hash - let valset = Valset { - nonce: 1, - reward_amount: u256!(0), - reward_token: None, - members: vec![ - ValsetMember { - eth_address: "0xc783df8a850f42e7F7e57013759C285caa701eB6" - .parse() - .unwrap(), - - power: 3333, - }, - ValsetMember { - eth_address: "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4" - .parse() - .unwrap(), - - power: 3333, - }, - ValsetMember { - eth_address: "0xE5904695748fe4A84b40b3fc79De2277660BD1D3" - .parse() - .unwrap(), - - power: 3333, - }, - ], - }; - let checkpoint = encode_valset_confirm("foo".to_string(), &valset); + // check with empty reward + valset.reward_amount = u256!(0); + valset.reward_denom = "".to_string(); + let checkpoint = encode_valset_confirm(gravity_id.clone(), &valset); let checkpoint_hash = Keccak256::digest(&checkpoint); + + let correct_hash: Vec = + hex_str_to_bytes("0xa2c8dc58c06fa959763bffd4c8fe8668869b7b5c866a7b0f0f1739b92a6cd5d1") + .unwrap(); + assert_eq!( + bytes_to_hex_str(&correct_hash), + bytes_to_hex_str(&checkpoint_hash) + ); + + // check with updated and same hash + valset.nonce = 1; + let checkpoint = encode_valset_confirm(gravity_id, &valset); + let checkpoint_hash = Keccak256::digest(&checkpoint); + + let correct_hash: Vec = + hex_str_to_bytes("0xa2c8dc58c06fa959763bffd4c8fe8668869b7b5c866a7b0f0f1739b92a6cd5d1") + .unwrap(); assert_ne!( bytes_to_hex_str(&correct_hash), bytes_to_hex_str(&checkpoint_hash) - ) + ); } #[test] diff --git a/orchestrator/ethereum_gravity/src/submit_batch.rs b/orchestrator/ethereum_gravity/src/submit_batch.rs index 699703365..b108e17f6 100644 --- a/orchestrator/ethereum_gravity/src/submit_batch.rs +++ b/orchestrator/ethereum_gravity/src/submit_batch.rs @@ -142,26 +142,6 @@ fn encode_batch_payload( let sig_arrays = to_arrays(sig_data); let (amounts, destinations, fees) = batch.get_checkpoint_values(); - // Solidity function signature - // function submitBatch( - // // The validators that approve the batch and new valset encoded as a ValsetArgs struct - // address[] memory _currentValidators, - // uint256[] memory _currentPowers, - // uint256 _currentValsetNonce, - // uint256 _rewardAmount, - // address _rewardToken, - // - // // These are arrays of the parts of the validators signatures - // uint8[] memory _v, - // bytes32[] memory _r, - // bytes32[] memory _s, - // // The batch of transactions - // uint256[] memory _amounts, - // address[] memory _destinations, - // uint256[] memory _fees, - // uint256 _batchNonce, - // address _tokenContract, - // uint256 _batchTimeout let tokens = &[ current_valset_token, sig_arrays.sigs, @@ -172,7 +152,7 @@ fn encode_batch_payload( batch.token_contract.into(), batch.batch_timeout.into(), ]; - let payload = encode_call("submitBatch((address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[],uint256[],address[],uint256[],uint256,address,uint256)", + let payload = encode_call("submitBatch((address[],uint256[],uint256,uint256,string),(uint8,bytes32,bytes32)[],uint256[],address[],uint256[],uint256,address,uint256)", tokens).unwrap(); trace!("Tokens {:?}", tokens); diff --git a/orchestrator/ethereum_gravity/src/test_cases/valset_update_test.rs b/orchestrator/ethereum_gravity/src/test_cases/valset_update_test.rs index 1af04c45e..725422ba3 100644 --- a/orchestrator/ethereum_gravity/src/test_cases/valset_update_test.rs +++ b/orchestrator/ethereum_gravity/src/test_cases/valset_update_test.rs @@ -54,7 +54,7 @@ mod tests { nonce: 0, members: members0, reward_amount: u256!(0), - reward_token: None, + reward_denom: "".to_string(), }; powers[0] -= 3; @@ -70,7 +70,7 @@ mod tests { nonce: 1, members: members1, reward_amount: u256!(0), - reward_token: None, + reward_denom: "".to_string(), }; let mut confirms = Vec::new(); @@ -85,9 +85,14 @@ mod tests { }) } - let encoded_update_bytes = - encode_valset_update_payload(&valset1, &valset0, &confirms, gravity_id.to_string()) - .unwrap(); + let encoded_update_bytes = encode_valset_update_payload( + &valset1, + &valset0, + some_cosmos_address, + &confirms, + gravity_id.to_string(), + ) + .unwrap(); assert_eq!( bytes_to_hex_str(&encoded_update_bytes), diff --git a/orchestrator/ethereum_gravity/src/utils.rs b/orchestrator/ethereum_gravity/src/utils.rs index 7f296a58b..3cda32b04 100644 --- a/orchestrator/ethereum_gravity/src/utils.rs +++ b/orchestrator/ethereum_gravity/src/utils.rs @@ -1,7 +1,6 @@ use gravity_utils::{ clarity::{ abi::{encode_call, Token}, - constants::ZERO_ADDRESS, u256, Address as EthAddress, Uint256, }, types::*, @@ -158,23 +157,20 @@ impl GasCost { /// uint256[] powers; /// uint256 valsetNonce; /// uint256 rewardAmount; -/// address rewardToken; +/// string rewardDenom; // } pub fn encode_valset_struct(valset: &Valset) -> Token { let (addresses, powers) = valset.to_arrays(); let nonce = valset.nonce; let reward_amount = valset.reward_amount; - // the zero address represents 'no reward' in this case we have replaced it with a 'none' - // so that it's easy to identify if this validator set has a reward or not. Now that we're - // going to encode it for the contract call we need return it to the magic value the contract - // expects. - let reward_token = valset.reward_token.unwrap_or(ZERO_ADDRESS); + // the zero address represents 'no reward' + let reward_denom = valset.reward_denom.to_string(); let struct_tokens = &[ addresses.into(), powers.into(), nonce.into(), reward_amount.into(), - reward_token.into(), + Token::String(reward_denom), ]; Token::Struct(struct_tokens.to_vec()) } diff --git a/orchestrator/ethereum_gravity/src/valset_update.rs b/orchestrator/ethereum_gravity/src/valset_update.rs index 09cc7840c..4ffb7d360 100644 --- a/orchestrator/ethereum_gravity/src/valset_update.rs +++ b/orchestrator/ethereum_gravity/src/valset_update.rs @@ -2,8 +2,10 @@ use std::{cmp::min, time::Duration}; use gravity_utils::{ clarity::{ - abi::encode_call, u256, Address as EthAddress, PrivateKey as EthPrivateKey, Uint256, + abi::{encode_call, Token}, + u256, Address as EthAddress, PrivateKey as EthPrivateKey, Uint256, }, + deep_space::address::Address as CosmosAddress, error::GravityError, types::*, u64_array_bigints, @@ -30,6 +32,7 @@ pub async fn send_eth_valset_update( gravity_contract_address: EthAddress, gravity_id: String, our_eth_key: EthPrivateKey, + reward_recipient: CosmosAddress, ) -> Result<(), GravityError> { let old_nonce = old_valset.nonce; let new_nonce = new_valset.nonce; @@ -48,7 +51,13 @@ pub async fn send_eth_valset_update( return Ok(()); } - let payload = encode_valset_update_payload(new_valset, old_valset, confirms, gravity_id)?; + let payload = encode_valset_update_payload( + new_valset, + old_valset, + reward_recipient, + confirms, + gravity_id, + )?; let tx = web3 .send_transaction( @@ -80,6 +89,7 @@ pub async fn send_eth_valset_update( } /// Returns the cost in Eth of sending this valset update +#[allow(clippy::too_many_arguments)] pub async fn estimate_valset_cost( new_valset: &Valset, old_valset: &Valset, @@ -88,6 +98,7 @@ pub async fn estimate_valset_cost( gravity_contract_address: EthAddress, gravity_id: String, our_eth_key: EthPrivateKey, + reward_recipient: CosmosAddress, ) -> Result { let our_eth_address = our_eth_key.to_address(); let our_balance = web3.eth_get_balance(our_eth_address).await?; @@ -103,7 +114,14 @@ pub async fn estimate_valset_cost( gas: Some(gas_limit.into()), value: Some(u256!(0).into()), data: Some( - encode_valset_update_payload(new_valset, old_valset, confirms, gravity_id)?.into(), + encode_valset_update_payload( + new_valset, + old_valset, + reward_recipient, + confirms, + gravity_id, + )? + .into(), ), }) .await?; @@ -119,6 +137,7 @@ pub async fn estimate_valset_cost( pub fn encode_valset_update_payload( new_valset: &Valset, old_valset: &Valset, + reward_recipient: CosmosAddress, confirms: &[ValsetConfirmResponse], gravity_id: String, ) -> Result, GravityError> { @@ -133,27 +152,13 @@ pub fn encode_valset_update_payload( let sig_data = old_valset.order_sigs(&hash, confirms)?; let sig_arrays = to_arrays(sig_data); - // Solidity function signature - // function updateValset( - // // The new version of the validator set encoded as a ValsetArgsStruct - // address[] memory _newValidators, - // uint256[] memory _newPowers, - // uint256 _newValsetNonce, - // uint256 _newRewardAmount, - // address _newRewardToken, - // - // // The current validators that approve the change encoded as a ValsetArgsStruct - // // note that these rewards where *already* issued but we must pass it back in for the hash - // address[] memory _currentValidators, - // uint256[] memory _currentPowers, - // uint256 _currentValsetNonce, - // uint256 _currentRewardAmount, - // address _currentRewardToken, - // - // // These are arrays of the parts of the current validator's signatures - // Signature[] _sigs, - let tokens = &[new_valset_token, old_valset_token, sig_arrays.sigs]; - let payload = encode_call("updateValset((address[],uint256[],uint256,uint256,address),(address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[])", + let tokens = &[ + new_valset_token, + old_valset_token, + sig_arrays.sigs, + Token::String(reward_recipient.to_string()), + ]; + let payload = encode_call("updateValset((address[],uint256[],uint256,uint256,string),(address[],uint256[],uint256,uint256,string),(uint8,bytes32,bytes32)[],string)", tokens).unwrap(); Ok(payload) diff --git a/orchestrator/ethereum_gravity/test_files/valset_update_rlp b/orchestrator/ethereum_gravity/test_files/valset_update_rlp index 2be7f014d..705993cef 100644 --- a/orchestrator/ethereum_gravity/test_files/valset_update_rlp +++ b/orchestrator/ethereum_gravity/test_files/valset_update_rlp @@ -1 +1 @@ -0xaca6b1c10000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000208000000000000000000000000000000000000000000000000000000000000040a000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000001060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d300000000000000000000000092561f28ec438ee9831d00d1d59fbdc981b762b20000000000000000000000002ffd013aaa7b5a7da93336c2251075202b33fb2b0000000000000000000000009fc9c2dfba3b6cf204c37a5f690619772b926e39000000000000000000000000fbc51a9582d031f2ceaad3959256596c5d3a546800000000000000000000000084fae3d3cba24a97817b2a18c2421d462dbbce9f000000000000000000000000fa3bdc8709226da0da13a4d904c8b66f16c3c8ba0000000000000000000000006c365935ca8710200c7595f0a72eb6023a7706cd000000000000000000000000d7de703d9bbc4602242d0f3149e5ffcd30eb3adf000000000000000000000000532792b73c0c6e7565912e7039c59986f7e1dd1f000000000000000000000000ea960515f8b4c237730f028cbacf0a28e7f45de00000000000000000000000003d91185a02774c70287f6c74dd26d13dfb58ff160000000000000000000000005585738127d12542a8fd6c71c19d2e4cecdab08a0000000000000000000000000e0b5a3f244686cf9e7811754379b9114d42f78b000000000000000000000000704cf59b16fd50efd575342b46ce9c5e07076a4a0000000000000000000000000a057a7172d0466aef80976d7e8c80647dfd35e300000000000000000000000068dfc526037e9030c8f813d014919cc89e7d4d7400000000000000000000000026c43a1d431a4e5ee86cd55ed7ef9edf3641e901000000000000000000000000cb9791b433028b8af808afae87a5c462b19bdc5c000000000000000000000000de7a54a8f8a7b38abd82ea37a0b16a40ccc2a88d000000000000000000000000387f1112a41afe50f3ce5994aca73d74b955be2d00000000000000000000000092a1cdddc0318a9b5dcbe461fd8a5db1ac710cee0000000000000000000000006caf3c6ca0886f2eabbd0db9affb4dffcd13d7c7000000000000000000000000679a451be81dd0900b67a0cbff83ce916ee225c00000000000000000000000008f67cba9fe1963d10292af8a1cb55c2218dd2ee6000000000000000000000000cc5e7d91cc4ac0c5d2f5faca979d656836addf92000000000000000000000000b0238677d6943855295bb9b6bf02f3990b8f26910000000000000000000000003240a90b048a226e6e9a4be508ccdf1af0093e8f000000000000000000000000036883cefb101a5b2fb9d04a0f7bd79b30eedecf00000000000000000000000093850baa3b36c5f38259dbcb1dfe41a51f2220c9000000000000000000000000ca049f8d721e9e7678780de0b9a10bbf35484725000000000000000000000000d01187d2f34019dd46e620972a7856070e59798800000000000000000000000079f00c728e2e1b04654d7f29644e0f65c4d4e60600000000000000000000000058db31c219fb60754106eb39f01113420922b3ee000000000000000000000000db199b549f079634353f4ed311fbd618d51f7151000000000000000000000000a8585cb41afac4acc564b1340cbc056b001a206c000000000000000000000000aa43af6ec4a697cb9080701b2ecefbe6a55e5c06000000000000000000000000f0ef51ed1e29ac1b9e9223fb48055d9c4dfb6cc6000000000000000000000000a577c2e6df1c5ace2f196616b0b4f57a5a5a3020000000000000000000000000b7b2484267460bfe170e6ea9479e10191563b11c0000000000000000000000009aed48caadcf32640a97cc6c20429f306086142e000000000000000000000000543d3c730079576c316558bfce1261e03bca04ba00000000000000000000000082deef4e2a7eefe4372876681057265a9fec449f000000000000000000000000a740131837b6d9047b04639ea88b9a7b35e5ed48000000000000000000000000732f3a6b3ca0f434fb2acda13947ff76a485424500000000000000000000000027556e6d4884684bb38d315544625c4a86af599f00000000000000000000000090f3bd80ccdf4cc8ab6609b20a221374430f90ef0000000000000000000000008d778b8365cea102ba549d5d7df0841ad7d67d77000000000000000000000000752940f08d52a4d7d8563ad3b2d8705c1684ac4b000000000000000000000000302674a7d30fddd4429d5fc0f59a986d08e7a2b9000000000000000000000000ce2fa55f119bfba1930f8a715d9d3e9cba00f97c000000000000000000000000e3df710194d6a63f3f75122bacb8ea6709b96e790000000000000000000000006e26fd8e46d3f74d16ef95ba43bdd556270069eb000000000000000000000000b65ed96ae0f71d1f1d05d8dc5dc0f9579f2c2d01000000000000000000000000e2c9a10c28624175c833f51da5e23af6d5f16f79000000000000000000000000e04c01bf8ab45e7e6bc08bda492bff731cb03cd8000000000000000000000000b68bd07399080046b3bca6292c509750de2ba363000000000000000000000000b79d1319fc753aae93a9777303134df617ec49fd0000000000000000000000006aa97edf1dcb23d50f0443366f4299f887cfae9a000000000000000000000000ca9f8aebac39edde761f5fd369f8ade1c8237f32000000000000000000000000a1e5f8f420a569b8f522895fed1bd80a4e4b43170000000000000000000000003e1fad1561aa55fde0631bdcabb9d318e66dbbf80000000000000000000000008cccf9542187d8d01ea8cdd7707e164016bf465900000000000000000000000005365245fd9bd47f1219ef8246636b2cc9253f4a0000000000000000000000002609a961883af111a04378356991f333f76791a6000000000000000000000000c1144baac2b15745f289ab6eee2f66afd2a478350000000000000000000000002a71999f4128bec31853131af1c7046a20797c730000000000000000000000003f3c44b845a1e877bedff9c88b6d7128d507d6ae0000000000000000000000009b79cd118008e3c15c91ef580fb27192abccb16300000000000000000000000001ec4766fcdd477ddf260171f1f7aab30aaff75a00000000000000000000000006cfb0368c9b66402174b406aebdbe075f5915c30000000000000000000000001f850a925a102f40aba92ad3ee5f30291d5a260b000000000000000000000000c8b7d081a9d6b7ccbda1af96a3700746f39839ae000000000000000000000000cc8984a17735454c53c574f9033f7a703f425048000000000000000000000000ad3f4ec8d7dc7cc1a37e049f68b21787f5706a72000000000000000000000000d5745b8283b47c26aba350c260b1221b62c683df000000000000000000000000f2f635f34bc373e97f778a4e9f7b082e64ad2f24000000000000000000000000884b95bcf31ac67fc07a2ca38ac432ac3dd623ec000000000000000000000000fa2a5fd9ac9c8e6160a84e3b177b378a853396b6000000000000000000000000a7f68b484c5050f971a8a360b5b44d6453be2c83000000000000000000000000ca4a4009ac47375177cbae20965f5d3eafd7f394000000000000000000000000610c239140f02a73d92bbec930c58b48359163dc00000000000000000000000028af19a1a3979182057dc0d5193bd852fd5884790000000000000000000000004c49672ccae0316b88b2fa5cca8296eda5c6c035000000000000000000000000279cbbd20a8fd28cba6a7a0e288e44cd6723030600000000000000000000000091ccc56bc81210c5ae58737377dbf7df61ac62bd0000000000000000000000008411a933ef1df8bd6b9b4ede1eb313b764bf3069000000000000000000000000e2b5b093993f05f5def6306a127f07aea15da1e90000000000000000000000003bd55f92454ffb7e74064e674e3310849b08022f000000000000000000000000aa1b1e373eb51b4b46ed0725f951f45a060aa6fa000000000000000000000000048f831a1d305899696833e43a277fe51ba4c422000000000000000000000000531ff58c527fa35a3a69ba44e11a56376f1d4e52000000000000000000000000a68b7db9de29c71d76045ecd1df336d9a18b610d00000000000000000000000002f232708bae9418fa18406aa896b0d75f8bfeb4000000000000000000000000d70769cafa4bc86365eee2549d48e9cb21dbbac4000000000000000000000000ba7cfb373b94afa6b1342ceed09dcbe474200231000000000000000000000000a95ce833e0c71fe1f690a2408866788479d77421000000000000000000000000a55f348325a9a0eacf71e98aedfcadd4d0afe5860000000000000000000000006b2a7799bfb2636b3246a79a36300092df6e8a5f0000000000000000000000008827026627924a8ca5e37d2364f054e0276bddf6000000000000000000000000eddc8e20b44bc2313117a0befb76086b1159f808000000000000000000000000830a8f97bba7c7558ae9045414407e00733bb627000000000000000000000000bfe43ba10f238b6a50923dd7e77114fe820d3eeb0000000000000000000000004cba62556364705480679aea0f49a3ad5dd14902000000000000000000000000a5be76c9b8416f93b23251279a34bb1b79adfc03000000000000000000000000bd5d7df0349ff9671e36ec5545e849cbb93ac7fa0000000000000000000000001786e3fa4005195cb2e2e7f1e53bb064b22a6f91000000000000000000000000104f3d52690f941999bf8a2f0e940233a6b08c96000000000000000000000000f53aed24fbaa33c1c8a7fa06e447ef042bd8cbbd000000000000000000000000fd012bdc1b9759dfb16ff66db6bce35a03fc1e8b0000000000000000000000003de084e4dab811bb28772d62a7f01ec075a8f394000000000000000000000000d5968a370ebf06a953096c2ff8866f2726b89ba30000000000000000000000003b7aaf86641c4834fc8356308be56e5760e4adae000000000000000000000000822dd57f3f921c40d907e14301824055b8b74df600000000000000000000000034c0efce1207da82659ce0ce0aaf06946aa7ab2a000000000000000000000000d75d1629c726756216b663c5507e88d7441a986b000000000000000000000000b00814592d667c8bf24d47de7d7c013dc83bebfc000000000000000000000000ba4f4ef59261245852fbedda5596775d374495cc00000000000000000000000085d360f5ea6ec190c623ff522a8c1e48f903a89c0000000000000000000000003ab0add61c58354c5b111f5ddb2dbce124b15bc30000000000000000000000005e1cdd3069dcbaac9ef9047dcbdce5e589c36c1a0000000000000000000000009b0e45b85167a7fb6b4d53a9f7abaf3d0d2febe70000000000000000000000007032521a461954bc6bcadb6948133020032786c3000000000000000000000000000000000000000000000000000000000000007d00000000000000000000000000000000000000000000000000000000121965e8000000000000000000000000000000000000000000000000000000000fe5c9c8000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000b3d083f000000000000000000000000000000000000000000000000000000000a8c15bc000000000000000000000000000000000000000000000000000000000a6b511f00000000000000000000000000000000000000000000000000000000082a99870000000000000000000000000000000000000000000000000000000007fcb9780000000000000000000000000000000000000000000000000000000007f62bbf0000000000000000000000000000000000000000000000000000000007c1bdf700000000000000000000000000000000000000000000000000000000064c2fc600000000000000000000000000000000000000000000000000000000062b6b290000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000573eaed0000000000000000000000000000000000000000000000000000000004f7663200000000000000000000000000000000000000000000000000000000047ae17700000000000000000000000000000000000000000000000000000000046dc60500000000000000000000000000000000000000000000000000000000045a1cda0000000000000000000000000000000000000000000000000000000003f1414a0000000000000000000000000000000000000000000000000000000003d07cad00000000000000000000000000000000000000000000000000000000038ef37300000000000000000000000000000000000000000000000000000000032617e300000000000000000000000000000000000000000000000000000000030be0ff0000000000000000000000000000000000000000000000000000000002e48ea90000000000000000000000000000000000000000000000000000000002d0e57e0000000000000000000000000000000000000000000000000000000002bd3c530000000000000000000000000000000000000000000000000000000002b020e10000000000000000000000000000000000000000000000000000000002a9932800000000000000000000000000000000000000000000000000000000025aee7c00000000000000000000000000000000000000000000000000000000024dd30a000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b79800000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022680b400000000000000000000000000000000000000000000000000000000021965420000000000000000000000000000000000000000000000000000000001cac0960000000000000000000000000000000000000000000000000000000001b7176b0000000000000000000000000000000000000000000000000000000001a36e40000000000000000000000000000000000000000000000000000000000182a9a300000000000000000000000000000000000000000000000000000000017c1bea00000000000000000000000000000000000000000000000000000000016f007800000000000000000000000000000000000000000000000000000000016872bf000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000147ae2200000000000000000000000000000000000000000000000000000000014120690000000000000000000000000000000000000000000000000000000001205bcc000000000000000000000000000000000000000000000000000000000113405a00000000000000000000000000000000000000000000000000000000010624e80000000000000000000000000000000000000000000000000000000000ff972f0000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000ebee040000000000000000000000000000000000000000000000000000000000e5604b0000000000000000000000000000000000000000000000000000000000ded2920000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d1b7200000000000000000000000000000000000000000000000000000000000cb29670000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000be0df50000000000000000000000000000000000000000000000000000000000b7803c0000000000000000000000000000000000000000000000000000000000b0f2830000000000000000000000000000000000000000000000000000000000aa64ca0000000000000000000000000000000000000000000000000000000000a3d71100000000000000000000000000000000000000000000000000000000009d4958000000000000000000000000000000000000000000000000000000000096bb9f000000000000000000000000000000000000000000000000000000000096bb9f0000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de6000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000083127400000000000000000000000000000000000000000000000000000000007c84bb000000000000000000000000000000000000000000000000000000000075f70200000000000000000000000000000000000000000000000000000000006f6949000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000005bc01e00000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000553265000000000000000000000000000000000000000000000000000000000055326500000000000000000000000000000000000000000000000000000000004816f3000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a00000000000000000000000000000000000000000000000000000000003afb810000000000000000000000000000000000000000000000000000000000346dc80000000000000000000000000000000000000000000000000000000000346dc800000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002752560000000000000000000000000000000000000000000000000000000000275256000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d00000000000000000000000000000000000000000000000000000000001a36e400000000000000000000000000000000000000000000000000000000001a36e4000000000000000000000000000000000000000000000000000000000013a92b00000000000000000000000000000000000000000000000000000000000d1b720000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000001060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d300000000000000000000000092561f28ec438ee9831d00d1d59fbdc981b762b20000000000000000000000002ffd013aaa7b5a7da93336c2251075202b33fb2b0000000000000000000000009fc9c2dfba3b6cf204c37a5f690619772b926e39000000000000000000000000fbc51a9582d031f2ceaad3959256596c5d3a546800000000000000000000000084fae3d3cba24a97817b2a18c2421d462dbbce9f000000000000000000000000fa3bdc8709226da0da13a4d904c8b66f16c3c8ba0000000000000000000000006c365935ca8710200c7595f0a72eb6023a7706cd000000000000000000000000d7de703d9bbc4602242d0f3149e5ffcd30eb3adf000000000000000000000000532792b73c0c6e7565912e7039c59986f7e1dd1f000000000000000000000000ea960515f8b4c237730f028cbacf0a28e7f45de00000000000000000000000003d91185a02774c70287f6c74dd26d13dfb58ff160000000000000000000000005585738127d12542a8fd6c71c19d2e4cecdab08a0000000000000000000000000e0b5a3f244686cf9e7811754379b9114d42f78b000000000000000000000000704cf59b16fd50efd575342b46ce9c5e07076a4a0000000000000000000000000a057a7172d0466aef80976d7e8c80647dfd35e300000000000000000000000068dfc526037e9030c8f813d014919cc89e7d4d7400000000000000000000000026c43a1d431a4e5ee86cd55ed7ef9edf3641e901000000000000000000000000cb9791b433028b8af808afae87a5c462b19bdc5c000000000000000000000000de7a54a8f8a7b38abd82ea37a0b16a40ccc2a88d000000000000000000000000387f1112a41afe50f3ce5994aca73d74b955be2d00000000000000000000000092a1cdddc0318a9b5dcbe461fd8a5db1ac710cee0000000000000000000000006caf3c6ca0886f2eabbd0db9affb4dffcd13d7c7000000000000000000000000679a451be81dd0900b67a0cbff83ce916ee225c00000000000000000000000008f67cba9fe1963d10292af8a1cb55c2218dd2ee6000000000000000000000000cc5e7d91cc4ac0c5d2f5faca979d656836addf92000000000000000000000000b0238677d6943855295bb9b6bf02f3990b8f26910000000000000000000000003240a90b048a226e6e9a4be508ccdf1af0093e8f000000000000000000000000036883cefb101a5b2fb9d04a0f7bd79b30eedecf00000000000000000000000093850baa3b36c5f38259dbcb1dfe41a51f2220c9000000000000000000000000ca049f8d721e9e7678780de0b9a10bbf35484725000000000000000000000000d01187d2f34019dd46e620972a7856070e59798800000000000000000000000079f00c728e2e1b04654d7f29644e0f65c4d4e60600000000000000000000000058db31c219fb60754106eb39f01113420922b3ee000000000000000000000000db199b549f079634353f4ed311fbd618d51f7151000000000000000000000000a8585cb41afac4acc564b1340cbc056b001a206c000000000000000000000000aa43af6ec4a697cb9080701b2ecefbe6a55e5c06000000000000000000000000f0ef51ed1e29ac1b9e9223fb48055d9c4dfb6cc6000000000000000000000000a577c2e6df1c5ace2f196616b0b4f57a5a5a3020000000000000000000000000b7b2484267460bfe170e6ea9479e10191563b11c0000000000000000000000009aed48caadcf32640a97cc6c20429f306086142e000000000000000000000000543d3c730079576c316558bfce1261e03bca04ba00000000000000000000000082deef4e2a7eefe4372876681057265a9fec449f000000000000000000000000a740131837b6d9047b04639ea88b9a7b35e5ed48000000000000000000000000732f3a6b3ca0f434fb2acda13947ff76a485424500000000000000000000000027556e6d4884684bb38d315544625c4a86af599f00000000000000000000000090f3bd80ccdf4cc8ab6609b20a221374430f90ef0000000000000000000000008d778b8365cea102ba549d5d7df0841ad7d67d77000000000000000000000000752940f08d52a4d7d8563ad3b2d8705c1684ac4b000000000000000000000000302674a7d30fddd4429d5fc0f59a986d08e7a2b9000000000000000000000000ce2fa55f119bfba1930f8a715d9d3e9cba00f97c000000000000000000000000e3df710194d6a63f3f75122bacb8ea6709b96e790000000000000000000000006e26fd8e46d3f74d16ef95ba43bdd556270069eb000000000000000000000000b65ed96ae0f71d1f1d05d8dc5dc0f9579f2c2d01000000000000000000000000e2c9a10c28624175c833f51da5e23af6d5f16f79000000000000000000000000e04c01bf8ab45e7e6bc08bda492bff731cb03cd8000000000000000000000000b68bd07399080046b3bca6292c509750de2ba363000000000000000000000000b79d1319fc753aae93a9777303134df617ec49fd0000000000000000000000006aa97edf1dcb23d50f0443366f4299f887cfae9a000000000000000000000000ca9f8aebac39edde761f5fd369f8ade1c8237f32000000000000000000000000a1e5f8f420a569b8f522895fed1bd80a4e4b43170000000000000000000000003e1fad1561aa55fde0631bdcabb9d318e66dbbf80000000000000000000000008cccf9542187d8d01ea8cdd7707e164016bf465900000000000000000000000005365245fd9bd47f1219ef8246636b2cc9253f4a0000000000000000000000002609a961883af111a04378356991f333f76791a6000000000000000000000000c1144baac2b15745f289ab6eee2f66afd2a478350000000000000000000000002a71999f4128bec31853131af1c7046a20797c730000000000000000000000003f3c44b845a1e877bedff9c88b6d7128d507d6ae0000000000000000000000009b79cd118008e3c15c91ef580fb27192abccb16300000000000000000000000001ec4766fcdd477ddf260171f1f7aab30aaff75a00000000000000000000000006cfb0368c9b66402174b406aebdbe075f5915c30000000000000000000000001f850a925a102f40aba92ad3ee5f30291d5a260b000000000000000000000000c8b7d081a9d6b7ccbda1af96a3700746f39839ae000000000000000000000000cc8984a17735454c53c574f9033f7a703f425048000000000000000000000000ad3f4ec8d7dc7cc1a37e049f68b21787f5706a72000000000000000000000000d5745b8283b47c26aba350c260b1221b62c683df000000000000000000000000f2f635f34bc373e97f778a4e9f7b082e64ad2f24000000000000000000000000884b95bcf31ac67fc07a2ca38ac432ac3dd623ec000000000000000000000000fa2a5fd9ac9c8e6160a84e3b177b378a853396b6000000000000000000000000a7f68b484c5050f971a8a360b5b44d6453be2c83000000000000000000000000ca4a4009ac47375177cbae20965f5d3eafd7f394000000000000000000000000610c239140f02a73d92bbec930c58b48359163dc00000000000000000000000028af19a1a3979182057dc0d5193bd852fd5884790000000000000000000000004c49672ccae0316b88b2fa5cca8296eda5c6c035000000000000000000000000279cbbd20a8fd28cba6a7a0e288e44cd6723030600000000000000000000000091ccc56bc81210c5ae58737377dbf7df61ac62bd0000000000000000000000008411a933ef1df8bd6b9b4ede1eb313b764bf3069000000000000000000000000e2b5b093993f05f5def6306a127f07aea15da1e90000000000000000000000003bd55f92454ffb7e74064e674e3310849b08022f000000000000000000000000aa1b1e373eb51b4b46ed0725f951f45a060aa6fa000000000000000000000000048f831a1d305899696833e43a277fe51ba4c422000000000000000000000000531ff58c527fa35a3a69ba44e11a56376f1d4e52000000000000000000000000a68b7db9de29c71d76045ecd1df336d9a18b610d00000000000000000000000002f232708bae9418fa18406aa896b0d75f8bfeb4000000000000000000000000d70769cafa4bc86365eee2549d48e9cb21dbbac4000000000000000000000000ba7cfb373b94afa6b1342ceed09dcbe474200231000000000000000000000000a95ce833e0c71fe1f690a2408866788479d77421000000000000000000000000a55f348325a9a0eacf71e98aedfcadd4d0afe5860000000000000000000000006b2a7799bfb2636b3246a79a36300092df6e8a5f0000000000000000000000008827026627924a8ca5e37d2364f054e0276bddf6000000000000000000000000eddc8e20b44bc2313117a0befb76086b1159f808000000000000000000000000830a8f97bba7c7558ae9045414407e00733bb627000000000000000000000000bfe43ba10f238b6a50923dd7e77114fe820d3eeb0000000000000000000000004cba62556364705480679aea0f49a3ad5dd14902000000000000000000000000a5be76c9b8416f93b23251279a34bb1b79adfc03000000000000000000000000bd5d7df0349ff9671e36ec5545e849cbb93ac7fa0000000000000000000000001786e3fa4005195cb2e2e7f1e53bb064b22a6f91000000000000000000000000104f3d52690f941999bf8a2f0e940233a6b08c96000000000000000000000000f53aed24fbaa33c1c8a7fa06e447ef042bd8cbbd000000000000000000000000fd012bdc1b9759dfb16ff66db6bce35a03fc1e8b0000000000000000000000003de084e4dab811bb28772d62a7f01ec075a8f394000000000000000000000000d5968a370ebf06a953096c2ff8866f2726b89ba30000000000000000000000003b7aaf86641c4834fc8356308be56e5760e4adae000000000000000000000000822dd57f3f921c40d907e14301824055b8b74df600000000000000000000000034c0efce1207da82659ce0ce0aaf06946aa7ab2a000000000000000000000000d75d1629c726756216b663c5507e88d7441a986b000000000000000000000000b00814592d667c8bf24d47de7d7c013dc83bebfc000000000000000000000000ba4f4ef59261245852fbedda5596775d374495cc00000000000000000000000085d360f5ea6ec190c623ff522a8c1e48f903a89c0000000000000000000000003ab0add61c58354c5b111f5ddb2dbce124b15bc30000000000000000000000005e1cdd3069dcbaac9ef9047dcbdce5e589c36c1a0000000000000000000000009b0e45b85167a7fb6b4d53a9f7abaf3d0d2febe70000000000000000000000007032521a461954bc6bcadb6948133020032786c3000000000000000000000000000000000000000000000000000000000000007d00000000000000000000000000000000000000000000000000000000121965eb000000000000000000000000000000000000000000000000000000000fe5c9c5000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000b3d083f000000000000000000000000000000000000000000000000000000000a8c15bc000000000000000000000000000000000000000000000000000000000a6b511f00000000000000000000000000000000000000000000000000000000082a99870000000000000000000000000000000000000000000000000000000007fcb9780000000000000000000000000000000000000000000000000000000007f62bbf0000000000000000000000000000000000000000000000000000000007c1bdf700000000000000000000000000000000000000000000000000000000064c2fc600000000000000000000000000000000000000000000000000000000062b6b290000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000573eaed0000000000000000000000000000000000000000000000000000000004f7663200000000000000000000000000000000000000000000000000000000047ae17700000000000000000000000000000000000000000000000000000000046dc60500000000000000000000000000000000000000000000000000000000045a1cda0000000000000000000000000000000000000000000000000000000003f1414a0000000000000000000000000000000000000000000000000000000003d07cad00000000000000000000000000000000000000000000000000000000038ef37300000000000000000000000000000000000000000000000000000000032617e300000000000000000000000000000000000000000000000000000000030be0ff0000000000000000000000000000000000000000000000000000000002e48ea90000000000000000000000000000000000000000000000000000000002d0e57e0000000000000000000000000000000000000000000000000000000002bd3c530000000000000000000000000000000000000000000000000000000002b020e10000000000000000000000000000000000000000000000000000000002a9932800000000000000000000000000000000000000000000000000000000025aee7c00000000000000000000000000000000000000000000000000000000024dd30a000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b79800000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022680b400000000000000000000000000000000000000000000000000000000021965420000000000000000000000000000000000000000000000000000000001cac0960000000000000000000000000000000000000000000000000000000001b7176b0000000000000000000000000000000000000000000000000000000001a36e40000000000000000000000000000000000000000000000000000000000182a9a300000000000000000000000000000000000000000000000000000000017c1bea00000000000000000000000000000000000000000000000000000000016f007800000000000000000000000000000000000000000000000000000000016872bf000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000147ae2200000000000000000000000000000000000000000000000000000000014120690000000000000000000000000000000000000000000000000000000001205bcc000000000000000000000000000000000000000000000000000000000113405a00000000000000000000000000000000000000000000000000000000010624e80000000000000000000000000000000000000000000000000000000000ff972f0000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000ebee040000000000000000000000000000000000000000000000000000000000e5604b0000000000000000000000000000000000000000000000000000000000ded2920000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d1b7200000000000000000000000000000000000000000000000000000000000cb29670000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000be0df50000000000000000000000000000000000000000000000000000000000b7803c0000000000000000000000000000000000000000000000000000000000b0f2830000000000000000000000000000000000000000000000000000000000aa64ca0000000000000000000000000000000000000000000000000000000000a3d71100000000000000000000000000000000000000000000000000000000009d4958000000000000000000000000000000000000000000000000000000000096bb9f000000000000000000000000000000000000000000000000000000000096bb9f0000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de6000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000083127400000000000000000000000000000000000000000000000000000000007c84bb000000000000000000000000000000000000000000000000000000000075f70200000000000000000000000000000000000000000000000000000000006f6949000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000005bc01e00000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000553265000000000000000000000000000000000000000000000000000000000055326500000000000000000000000000000000000000000000000000000000004816f3000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a00000000000000000000000000000000000000000000000000000000003afb810000000000000000000000000000000000000000000000000000000000346dc80000000000000000000000000000000000000000000000000000000000346dc800000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002752560000000000000000000000000000000000000000000000000000000000275256000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d00000000000000000000000000000000000000000000000000000000001a36e400000000000000000000000000000000000000000000000000000000001a36e4000000000000000000000000000000000000000000000000000000000013a92b00000000000000000000000000000000000000000000000000000000000d1b720000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000000000000000000000000000000000000000001c45d6596804f1114200ec74846a65ee276c9edef25e97eb6f40f41ce7099c11127eeb278e85cdfce99d33286baaf7c506545e41f84d35f5d497bc4886072a679f000000000000000000000000000000000000000000000000000000000000001bf19633cdcefe495d6d5190dece1fc63e856217d785bf14ecff535e13003fab6d63440ad2ef2a2c4e70809aa9deb00a94883bf9645b184cfd601c22676ad6aa71000000000000000000000000000000000000000000000000000000000000001ce4fda5b52688199dc6ea03ab5d05e1581480d86317b48e1943ba21b1414c0cce6c1501b62e1312613ed23163f026642ec4e68c676158580834c9ac4a731f6e45000000000000000000000000000000000000000000000000000000000000001b6c72dbc481d5390ac7764e0d8a5b6c1c5fbfda1d6ebe87ee42f276e2ed3485f56fb6328ab45928aa594e6666aabff675035d9a8a9aedee4aabdde64e8481b876000000000000000000000000000000000000000000000000000000000000001c4c52db5ad67a7dd0d8d85366a122593dce249c7112280e773a8bb74679a728bd2052e29b1bcc391141b37881f51882eefbdfbd1d84a4a4fa83f2a73388f3c741000000000000000000000000000000000000000000000000000000000000001c0450bd6269d4abc225e3f938e18aaa83d0793d09559bfb3f6739718557da236342408d8fea3789611e2992ad1db3211b476bdf05fa757c69cabbce5c4724a029000000000000000000000000000000000000000000000000000000000000001baf7292aa3955ea80f8d76b0caab2231965961118fb48a54a8bf91fdad5bae0c40ed4d179337f109b40e0ff65f664e9e0709ba43f605469eec5e1204e99faa101000000000000000000000000000000000000000000000000000000000000001c4077b8a560f53aef68c785ed82344288de4361ec80465b58d35b7182facf392f68c85f93e81ccb83ecfe2428d34fc23eb0b720112cb34edb0cd2ff4ffabda67e000000000000000000000000000000000000000000000000000000000000001c0561f183b1f8d6c3b8a815e77a5e1d8d113a0455313b7ea66156fc7cbab2b1af547703c9b312755df72057920cdd090b386d21b0d3bf43a7ce125395665a74e6000000000000000000000000000000000000000000000000000000000000001c082342a3de541ac8374fa033ca5084a305297748cbff7d37add357b2fd154f2d5a2f77810cb2a8b260d2d9ebd2566f82aaf9d9945c539727081350c48c7da6e0000000000000000000000000000000000000000000000000000000000000001b3fcd7c58acd472bad3c05362f2bbe339c4365728d5a044a8935093a20c54bacc13ce2d51cb196d22318e0b01cbe71d508b60c358e1fa240c4d1a5981eb238b1d000000000000000000000000000000000000000000000000000000000000001cbd1d5497e91dd6c2c1ac3cff50bece5a45aca5d76f55ff92b3e1cd0655744a9501ef37beb9c7d1f14381fa207565ae0ab918949f8e16be244ad16bd5c45408ee000000000000000000000000000000000000000000000000000000000000001ce6ac24ba8125d233719aaf003906c6eaf3c04035d39b09c511470d55196d0a9c7e65c7dea5b7d8e1ce86914e8065249adc1ced942427ee95cde1a902aaee352d000000000000000000000000000000000000000000000000000000000000001c9c7dce0122e6eb38e972d5e3636db000d96834643370b0560824eb46bfd45c9f036cebb27e9a0a39397f943ccc18f4a29e2a56741773df7c91a974a0c2860879000000000000000000000000000000000000000000000000000000000000001cbc07a50061732f50fa77ead4a80913826532dc55409e52a9089185dd2f976e464e1bc2cd15f6e82de5038d1eff4225688e435d4422eb9a69ad5be68c9a510b5b000000000000000000000000000000000000000000000000000000000000001c61033036153f546e7d3a388472dca2e7d19487fd0e9d352449298bed2ce0d3f422874d7e1333923caae32e414252af4ef37fbb1eaab543acd22c89805cf3790b000000000000000000000000000000000000000000000000000000000000001b845a90a85a6010b3307d22eb226328bd7692e12e8586cc3d0a5a8c55f998a10a48e195a229ed2aba2d81552350e1f3ffc8a47524d96c6b8c18ceab9167f93d84000000000000000000000000000000000000000000000000000000000000001cdb3d76901631fa04a00d7f29fcd45608f9df65e845a65ef2c76577d71bd2738e7c9011595d295d5ad6e0f86a24e6acc6c0ec1a45f936ad761367aafa70aa0c6c000000000000000000000000000000000000000000000000000000000000001c388e15f2d4b46aacecfb8b2042c162227e4e5f16b09e20996f9af3d0e55847234d825c2d4d60cf0aa6ae6db2ce910c44256a3fbc0860ea85b8efcf47a23d08b3000000000000000000000000000000000000000000000000000000000000001cc9c03dfa2494d06d24b7d0868b77352d534824db07049da3ccbd44190f8470b2165605d6965f5454928dedd5dad5b0dd622453b3472c9bf7671510020ec7b3bc000000000000000000000000000000000000000000000000000000000000001b92602e1c9ccd22d6a491c688384cc4651b241f932d9f8ea4b02737eb0a7552d91ad19ea4ae8548d66458dd06bd27a9982e7d5485ec57d34dac96e0d12bb2cabd000000000000000000000000000000000000000000000000000000000000001c638f4655632cfdd6331fc47fef67391e01fc51d8e14378edbd51c08f4561cf2f7b9c26ffe0ac02e3d9f7cc784819c1b4fdc8de3d74ffab253dded22fc40bb493000000000000000000000000000000000000000000000000000000000000001c23c73a8e63a157fa519ab6ac6ba14ff2a9b5bf174dee5a439482f86e844bb5fa26eeeacc78832c2fa3380befaa8eebd4165d4c81b64481381fa4ea0daf9a1d20000000000000000000000000000000000000000000000000000000000000001b2094b26e00a9469548d50b1b6a71313dbde8bee88d6167989d032ac67ffae5e31a7c94a2b5f4a88819b871d86fc79ae68b6608ba07787e2dbd5eaa1b3447fd5d000000000000000000000000000000000000000000000000000000000000001bcc6124d4124fb68affcd0df9da7fcb5c9beec1000b2cdde15a745b58156fa61f1f01544a956fad45f1ef535ebaa07427b11cbf1b399bbe763c38b358c92066d4000000000000000000000000000000000000000000000000000000000000001b44f945e6d977ec824866d26980bf2fcddecbd5dc09ee7f4945212fd883cd4dc8661a0c0ff9fc35683a724ac5418a1c372c1ff1fedbc0636f83c54494f0f5ec53000000000000000000000000000000000000000000000000000000000000001c6bc6ef92ce4161b985755492f7f2cfe1f5bbd1dc5d27d3de2cd1d5277e03058c434586b9a844c9022786c8c0669579760ec5367d9862abeb9bb30e0ef3ec5e1c000000000000000000000000000000000000000000000000000000000000001c9d0c55bfefa5741314036b0bbc661e9ed409b63c5b7dfe1ce1ebb72598b7ed0e1fe86bd4fba19955833c55b7a6178c3264358b7d5fb50d239861f28ac19ac846000000000000000000000000000000000000000000000000000000000000001cc601f83dce206f6c7753032397dc8703ac5efcf5bd25db2461449bbfcd1cb5c414950630bba4123aa5ba49736da7c2b433f3e8b2e95a561f88ef32c494f4e011000000000000000000000000000000000000000000000000000000000000001b30850855a8a360d19efb165cff008e430e9db3367903cdaf765258e90d1f258318dabf56a6a7747105cca144eac2e04812322224787232f0f955c81619c1e003000000000000000000000000000000000000000000000000000000000000001c46127246747e3176d867a59e8c8e18c7c9befa453c7b4f208bf47f121d4cd3f10ae7da5ebcf1ac8d2adb282130f47dd86412320a44fa360c5385c7b7ad4e2ae7000000000000000000000000000000000000000000000000000000000000001b022249dd6787d2085e53f76847d9f9b217731646a14ab8dfff9bcaabe497f19a0efb6253a5f75f613f55b12d628564e3ccfda467b1f22f6c7d93bbe025180197000000000000000000000000000000000000000000000000000000000000001bd672dd06d134dc2bcf01c11a63df34c46785285d3e84a4c774bc8fdbd0583adf69c356f3a9461c3f31b7b1251f9fd9deda4813df264c387d97164bb692bfe0e3000000000000000000000000000000000000000000000000000000000000001c2a6552b5dfadd2207c657d6845404445f8e15ed2d36f5b78443e05401069247f7d45ea2241a476df8ac06633e6876a1e189d747c880dfa8a278f893e60812aa6000000000000000000000000000000000000000000000000000000000000001c2b972fe7a2ebdfd198e471c54a6b0aa018b04ed016c0c42c3ff544f41af0395067ceab4c49d941558b0ca699fdd411652af55d04192efe7f41b9615b3ce0acdf000000000000000000000000000000000000000000000000000000000000001cdbda067ef349d43d13a43cf83b84ee934bce300727ad66744cb3ee1a14bff83f7f8a3c8af28f9f2a0ed2d3a3d2c4adf4cfc7a4ad964c7daa91c3f73c0d4af278000000000000000000000000000000000000000000000000000000000000001c3802ccd3159eb8e3e305b7863e76a24d31710d630eb36609c87d062667a58b167ecf7038349cb2841b2a71357dc9c125a5099bae9a9b1ee722f0ba873913d92b000000000000000000000000000000000000000000000000000000000000001c20ecfb3677fc310812495bebc7ba121585630207906f170b29f53dc9296635487cb74c500c8b811c88dcc9e09a19ff8b77707be0f26c81d4d5f03a38532bde68000000000000000000000000000000000000000000000000000000000000001ca0d6da386bb3b6b3d7293b54b31b82c039e2bb3e3494e127ecdf21b06aaae0a41a7ab7e1616dca50a6ddf50a88f17c178496e1d793275a2e1a46a2d79d911e71000000000000000000000000000000000000000000000000000000000000001b214d0c29e5cdd5ae40949d0808b8b3bf22704698d308edfd26a021bc1be2122a6839360291dcfccf23adcb5457379c060c4003413754c71853da79bdca755572000000000000000000000000000000000000000000000000000000000000001c8e68c89f4054e1b80c5f727d9e5b6cb8af2ba42b7ef6f8c95a619947386a01b62320e98cad8ed817452cbb4913b30b151743047bbc0769d085d8d32fcf3c2ffe000000000000000000000000000000000000000000000000000000000000001b095b09c084307ca20f40a89cf7e2abd6126d7a4a387a8d3a42d03e6535762ac2700db338169b263a6ca9e4e14ca1839608e57af91c52f63b460973c735ecb585000000000000000000000000000000000000000000000000000000000000001c1c9bd2107bddedca9617ea0a4fdbb12f91d2263ec63d28eb3fe53cea7d3346820f1f22133d6a3c0d72beaaae65da1d33253c09b463c63f149dd9b53003e31407000000000000000000000000000000000000000000000000000000000000001b579618cf61c22aaa57c5939f6459f65a5be62898d40befb2e0ec410cc015911c6da106e2194d7cdb5efc93a6c1d809b49f49215c856f2f3db3770bcfc48f4803000000000000000000000000000000000000000000000000000000000000001cd84b2f61e573aae2921dedc31cb1ca0a3c5fb7735390f1759748eaf89e143b2869224bdfd6f629c6842775a33560b51626f491b08baac70485035ec4de8ad501000000000000000000000000000000000000000000000000000000000000001c03549b8c3d991024601852657f8fac0c1847ca3897e1597227cc37ed1fcfb44f01a97750ee18bc57e72f2625fae5f132190e355c6cebeb14f5efae7418438464000000000000000000000000000000000000000000000000000000000000001c4d804701ef17ac76c690358fb1eab10ed0125136345d644442bcdcb10def0228437c400c6b71005d27f3e5d1c7a960d0e16d18fc747ff1f062eac75cdbf3c054000000000000000000000000000000000000000000000000000000000000001c59798f8691be7e16d702c4d84da3aa55c7c49d9cc36452391b224677b5b49ca341e9b550076fcc9a09aa226a043bd1ca1cda57e5d651614bc48d81aba3238d87000000000000000000000000000000000000000000000000000000000000001c3fb2be4d2fcce7c6b8d44e891e26c4bf3902fc90d0d043564897b1212c33e8993df5342578709b563c065a732bfaecc98f188d30886bfdb8bf83419c8c2a13ff000000000000000000000000000000000000000000000000000000000000001b7fedb4d550afa5e9eb9cca1541d3fb4dddb33c3f8457c59c3f2043449372707c080a6b4ff4cf23fcb62a8c7af7ff6a2d35975e89f82321971af29bdf19d26831000000000000000000000000000000000000000000000000000000000000001ca901c66a3754c7d2a15837fa7f7fa55f58e7ee7e93c2c76026d54f11b4193c23080521648a080d413c574b6ca8996b7d784fe90595e5f679c09df1f4cb083e9a000000000000000000000000000000000000000000000000000000000000001b3a5e9ab820f6ee35f48969daeb56302dad0b48fd3b815fe295dd874758c5c72e2946c883790bf8cab9d896bb5650bc5933b6b4d3681ae87ca613bf32dfba46e7000000000000000000000000000000000000000000000000000000000000001c1b90cd5ecc18f4b82ba968a2241365c85647bb53153e6bcaf9ec72704ff7826752ce353131912553047073d4cc9fd9580c04d6e553b486333344162bef9d379a000000000000000000000000000000000000000000000000000000000000001c8bcd28278114cee2e12a9f8157c7d1d7166bb9f12c0d28e6c8f615bd0679d0864782413dffda0b5323992720d3de1e8f55cc862088ceb7d7fe4a1f51ca17a6d1000000000000000000000000000000000000000000000000000000000000001c616661796351f7354ac5b7ea069d3da1ef090ad301c8da64e480de9fbdd1e5f6273058996b1920a92c3c3d6ee3ece2bd6c8ba986740af7a8c5e78c7923987553000000000000000000000000000000000000000000000000000000000000001b7710b7b92047d74f2911f6651b090aa1dfc7fc3d8af9d6eb36a3065f3f4346fc4621dbcc7ecc1545b41688dc821d879868bb13dda9f416caf3c3053a0e24e7cc000000000000000000000000000000000000000000000000000000000000001bb2dcbf5206550000719af84c56cf4a7bbdedf4427f0d1a4cb7100185517df6e54412e244e38553a3354abd1942fbbb14ccce5de69020a65a528deaab3cc14b05000000000000000000000000000000000000000000000000000000000000001b4bb109430e1793ba155669b074ee092beac5d5360993b13e1e9b0fb8eae42d11538298c8205a42cd12eaf638ccf3d11dfdf080bafd8c665610cc972f4f3fb724000000000000000000000000000000000000000000000000000000000000001c795b04d3f574a3edd8a05afd65ce6996ce7a65a563a41cbd5a80017fbf1de4761a5428b4a3817cdfe4416595dad54efe991632da5967563a2c434dd2666c4e70000000000000000000000000000000000000000000000000000000000000001cfacae8700e21c2a83a4340c541605c4feefb9243612be8221a5df5239d801da25527dc4640e59147c643ac0f01d484d942d47c2787971f0365ac2a959cba6407000000000000000000000000000000000000000000000000000000000000001cc568a92f3d0cd843c24980cb7169b5d3e6aff7f2c94e32487dd220b58b569f8820e9b00749f161cb05d94ef131211936fad3ef95006caf94160de8e54661a03f000000000000000000000000000000000000000000000000000000000000001bb99cd74874977d21de442dcf99c7f4ef2fce057ba2eff24478495eb35daae75411ac9e180c968a4a9b9c365c4a35c278d8c80de0ea8b208dd814084345ade9c2000000000000000000000000000000000000000000000000000000000000001b2d449bf840fbe12022267545fcb5e7e0a2e947e060a0207199d3928fde1eed8c78e7c136e5f18168078b80e57a8b4dc229ae737f6b8c79337cc5710fbb3d6769000000000000000000000000000000000000000000000000000000000000001ca1838ea0c0df905e57d62cef56bfaeca18d78f66fff45e085581472a36bcbf5c7a695f6738f44aa54090e4281706d659329a18a07a1d26ee21adb62fcb809053000000000000000000000000000000000000000000000000000000000000001b1019eb4c9069d12d0038456f7eb1b1315369e19ba497f45e4b7c1906c538787e43c87c09a19b0d3f8f66cc0db8890e58fa024bebbcaade64a2aff6df3d1342e2000000000000000000000000000000000000000000000000000000000000001b25938c991d97b72c15ba6adb3d8da4ca54ea8cd7704dc0d54e4176cbba0e7a325bfde9f4c73d5ca02a19bce81fb18631701ff0e61fe6490053dd398a56d9b400000000000000000000000000000000000000000000000000000000000000001b640ed44e8f604215f10a83a897fad70938784d95e3b70a4d6f4a4ee3dc7434bb623f1a312997a1fd53687483ac189940cbae13b72ff3247ad70e9e9ac00b6ebf000000000000000000000000000000000000000000000000000000000000001c77940d4e7eb69d54fb006e847931c32d25ff4adcad6940cf6760f1ccd161e61d28d52ee37cda20fd077c08b8139d26c731532d0019479f4c39fb7c912a6bdeb6000000000000000000000000000000000000000000000000000000000000001cc86c7f74d6fdbc4558f5c9b3cd123de6b76ae197760c8f4cafae73b64566c9f97c7fef9d181bbeb99dccfef1c0da2250ce304ed730013ff9b54c0ae9d4b4d335000000000000000000000000000000000000000000000000000000000000001b85d2d5347b364880145afa5cb5776c7b12e9ff95a7e1e734bf9fa146ad5ec85374fb358465f953a95193d151fb2f6b0db2a2bbaa09e06c36ccdd0b1ddb599931000000000000000000000000000000000000000000000000000000000000001bc9f5c3a9c6ed7e26a72663c6c0979df9b8ffd6d82a3ec0ecf0d527b26df8dc732f15c21dfbaecbe31b77fde118576e068e07b5cb4b6c8a5c9ce984d8bbf347c1000000000000000000000000000000000000000000000000000000000000001cf438cae33362a90d41d5cf20211ece0fd250eaa71468c032a550e4a32b38b3e3711b5e19d5e8f244120633b22153929e58d5361ae721103ea3f58d660ad8e3b8000000000000000000000000000000000000000000000000000000000000001b38e800971b3e12f603b99bd88582a208043abce93e1ba1113fb80b5ed66edca6480f0b122dbc85e67602500b13038c051466f1087110c9e8c27f511200b64b97000000000000000000000000000000000000000000000000000000000000001c03d966f2df38c1ba98e2e3b9648f4e884ff7eb282200a0e41ea833d5c6f4c45844dc313a29c4e404717ce9c923e13323d19689791ba350773680698c457cb750000000000000000000000000000000000000000000000000000000000000001b73f811961c7958fc3b5934027cce76fa8e94d05be53d598c4431788e9d3ae1442fb1fda37ecd3219d298ebe7b9921130a7694b9501863ffcf65c367ae8f57e6e000000000000000000000000000000000000000000000000000000000000001c2fe6492fde30a6a3a7d2569e36b70f41f2893f392899bd5893e741eaed8ae6d3134ebfd17505e9600447f9ad699a37e53a8a186cb5ee1e39ee5ce565836ecdba000000000000000000000000000000000000000000000000000000000000001ce267536de4d8858cb78c3354e282d77ba987c78b5fbcddaf521d8355563bb3f83faf35c05a03f9b6efeb07ca234217dd010c5a606ab967da3b391bcb30f0e842000000000000000000000000000000000000000000000000000000000000001b09981b70725efc7fcf290c19be05c6804b2224cac99ccae6e4f723b4337c65c62a6be1e78512e6c044435dd39e8553aa49fe83c7703fc953e3b0f87289525615000000000000000000000000000000000000000000000000000000000000001b029f1b13d556bc1e339c764737c889103fb3bb60aaf2a3873961dad3e460c5e340c0bb9a572a1a020f2e1dead547de106c6d9b88c652638ba789f236a01eaf2a000000000000000000000000000000000000000000000000000000000000001c740afbf75f792736e8769bc7726ff840b8aca9f6dd5a61dfa38e001c09a0f7cb3d294815e96d7599c4c247f57add11126030b15ac8137f0c79c2033266cfe3ae000000000000000000000000000000000000000000000000000000000000001c2f49735e60975920530a25f140ee808477f60a9b8f82b49fc83aba28125225be1ab6c68ec99f7ad3132b00613949e145200d987bbaf187c144844ec257abe525000000000000000000000000000000000000000000000000000000000000001ba0a744340f70eb477974b1f4125d26d8e55bbaf385e6fdbecb096d5492fe4b1f19cfba64923ac5e9501ec892d4b4716814d2eed726e22fb0725005814fd05ca5000000000000000000000000000000000000000000000000000000000000001cbc409e883081e27f6c26d4a851b833aacded3c59c471b9671f09931d7e6b6a222ef5333afa0b90e2f928ccca932fc68e0bb4548f12d21b258f869961086e0f9d000000000000000000000000000000000000000000000000000000000000001b14b22b3f782dcd1e62eee9ffadb6654fd3221b0c6259e25ca4a114c39ecc6a810ecbcf1306bd2a622ad0b0d79327ca75ad6db69cd73756476d066a9539b9dc8f000000000000000000000000000000000000000000000000000000000000001b0e251ab81eb0a448515c3df7299881a69eed0cad04d38cc2ec6dfadcdb5f3038183f6750c16b007df2ab7d6ff92174286b59af127d747426ddb4775998b7482a000000000000000000000000000000000000000000000000000000000000001c042b8f8462f09695eaa5bc9113a047fe4f5803cfcb8ac9d8882ee1e47f9697b833b1582947a86a6bfd65b0b9560c8891d4f287b568ac1a6d3dc1a3a4b73fb7bf000000000000000000000000000000000000000000000000000000000000001b49f7713c2e60f3ba7582a58035a4eaf6693d1a7643d2da0f0af390ea0462dc4c4c6692b0f3f9f814c5e75abac638a430c4bd66449258a0cb0e3ddab811202f5d000000000000000000000000000000000000000000000000000000000000001c4f3a27cf308fc3d7a675e20cd0940d1ede39f5a12d159d2776d4837668a518bc0f4442e1b5abff2ddee15fd00a9d5c64aad268627f8f3173d1a017502c978760000000000000000000000000000000000000000000000000000000000000001b20584266a67fa30add4e03cdb7cf661fcaaaf2eef71c783c3298efcb4c387a1b52a89d68a35929f5be172b787762fe17acb3c190ac73f241475de0a1357e38f9000000000000000000000000000000000000000000000000000000000000001bc9d02b8f3b25de0e2c6db7ede616da58781d50fd5a78f3f30af66200d43ff98e15b27e31e09749da232e549a4b2f9c6109ecbe1d386aad05c93a606a49f9867c000000000000000000000000000000000000000000000000000000000000001cb4f0bbbcf298ec729f226feec433e4067e97ac0d5c47dd3a093ce52b0d0350145df62e4edbc031863e1d77ecc83087cbca9809f1a5538dce54ff01e72de021b0000000000000000000000000000000000000000000000000000000000000001bd2abdcb0a2d6e8ac24e860e8f3d4903aaac5a02b199112ec7e0a6acbee61e788494e4bd76fced4cc7652180c7de0211cddc11e0f9278d17c28cb6b6b5bf9ebd0000000000000000000000000000000000000000000000000000000000000001b7946dea4af29774962af0c8be28478a3c67eefd3a225f031b27a1c783ca062a72ac060cb2a82552e94a412a9ac35e78771b09c9a5b0c463ff2c58e2baae597b1000000000000000000000000000000000000000000000000000000000000001c15086ccd9657ac8a45c27b81332ddd668948ad9e04e915877b610604c307f92d3fa7f9a2051cd7e4d140f0f0c2f31d0da523c53575749aa51f164ba4ef049421000000000000000000000000000000000000000000000000000000000000001cdd4639a0d2602bb8a3bfa168e262fd192f07cc5ff084fe23c426995d688ab348372685acfff932e49d9bd25d978551733fb60d3bf07c30d04705f854e2e27358000000000000000000000000000000000000000000000000000000000000001cdfa5901075897d8a7f3dfddf825619fd7fb64a43eef456364aa5431892259a1e1bf8615f59894fcfc485fb4f7bec9971266023c4ee5db17e97db61bc902e9d89000000000000000000000000000000000000000000000000000000000000001b20ab0ceb62e60caf598b829f775c01760aab447ac391b64afddc5d0e5e65679d6693966af1497c0a88c805949197cd48be67f80cb17840e83581df8003a0984f000000000000000000000000000000000000000000000000000000000000001cbba7a02e4cc1bb835311e376fb4546e3cccbccb23b4eb5db92b611029109a0ec380f5519496f98f6d2a12c456ec7e7223dd94a14199c37c603ed643bc3e33385000000000000000000000000000000000000000000000000000000000000001cd63d81ba77a41dc743801444ab807bb5e896822c1ef8840da7928ac05e1da1885b6cab8c3a4e876ca9b4481d869178fccbf90f42ba942641492da0cb4ed38820000000000000000000000000000000000000000000000000000000000000001be6af18a5c296661d58e546f1fca249a5136493562dc0f5662ccd6c3e1f9dc18b3ce2311ff094fab3494879423c069cc1e096cd0fcfcde4bd7b067ea220360fdf000000000000000000000000000000000000000000000000000000000000001cb2ae4fdba8ac9759a0a881400c7aa3d7b076ab81972db3c65ed25b5f24e3d89450bf80af29131a11f1450fe43fcdc04581570d28f93b668175a4dc0d0db2f503000000000000000000000000000000000000000000000000000000000000001b8f63126befd87d5a25bd8768152984677fb7f4bdaf80c45265aa958c262ccd9a57081cc237338b36295b13455689634dbf1b710bbf595eb42d0cb6f58d1b41a5000000000000000000000000000000000000000000000000000000000000001c41ab12f8056f580020f282d55b7520b7888dd1c8e6115e723e5d61237fe9b69708c05129fb3156be0ae93ac7cc69a9cad62e22b0660ee96bf8cf8390df67a4e1000000000000000000000000000000000000000000000000000000000000001b3022679c25c8e90a65c4301a39251591d915b0caf8cf234ea07e99a7d925dd1254ece473be7f863dfc76b6d47ab3183df8fceb2cc4f666cfa9999647928f2533000000000000000000000000000000000000000000000000000000000000001c96408eb05c0496f68ddad4ded7dad775b7c83f9dae31fd6b7af756c17b0ddb627b22f1280880dff832ccdd16f35745dc0b5fc9fc07edb9bdc068b1dc6ed53f73000000000000000000000000000000000000000000000000000000000000001bb6ead350c853039eb8fffca1dfe58699ba56c8742d5619650933f1024c28df0b5afccb2abe1e09a3e50b5972e7f2d162700da653e983e0d00fc315c1680a34ef000000000000000000000000000000000000000000000000000000000000001b0721466593d718a9fccf8095f132f3c90238f7091d365cf904bdd6e9e91a6e4431cd5f836f16ef0033ced47fdf2f27027b3cb6c9ad46f14cc01443646b289986000000000000000000000000000000000000000000000000000000000000001c32f847a9e4912bee70d781510241b2ac8ebef6600fd766ad53fc6f9c0365eb923d60402c6af6f80f07efaf1475fa8a08be381475a2c77899d0224ccd424845f8000000000000000000000000000000000000000000000000000000000000001c83abbde9169cd449daae4fe3d6275d2b61d233fc7dc74d16ab39504ee0e5fee43ede3a6bda66649b1ada3037eb64b32a8747478287141eab8010bde4fc5303a0000000000000000000000000000000000000000000000000000000000000001cdc596e67704bb3ca1602834cbfe2c9954ffcf81c8e4c4c871055183fd0c02c3c77507c1e55314d98b4c7c03a2ba79da1ea9052588c45519915a7e2de36dfef61000000000000000000000000000000000000000000000000000000000000001cb55dc0afd16bf3b6e0025400403858f6aca2c3ad0d82c54bfa07301ce622c44d0c1c44173fdd764e1495c5e3d1a1a85186d7ae61bb16cccf6ffeb4c725682315000000000000000000000000000000000000000000000000000000000000001ce2ad7710f0e343347e123caf857cce1ca41ec12fd0b3dc0aaa697d23ee961568687a8c27601c045662d13f21ef41fb3a3831c49d07787c44f4fc49b89737c0ac000000000000000000000000000000000000000000000000000000000000001c38826ee70dc3b3b9e320d516bdd8e12cff2e832258f3550cd6b1354d0696ba96451a2bd0b25e85d4c9ccf1025a23fcb3419f17c422959381463dcca12a36e8ee000000000000000000000000000000000000000000000000000000000000001cc8c6ef7512c8dfac2dd95019e951d9a88fc07349cd7e0788c7e776a8f88db88353d3b3fca93bacb0bcb6a5e2b671d85261cfe3fbb1ad2e1ca49b7fb5f18bde43000000000000000000000000000000000000000000000000000000000000001b909b24b509174a585949c3a2af4ad64a9f04a90d82e4408cce6c09046332c3ab6d3e7d7bcb8dfd3db3133f8d4e805695e397039ce2e61debcc56d16f4d349540000000000000000000000000000000000000000000000000000000000000001c0f33f6a6ec329625bfee1f9257b815d720fc6457eedea6e6a598c83ebec31d090fe68f2e9e62932cabb9c7ad031d120635977b733af3d457ca116167bc625543000000000000000000000000000000000000000000000000000000000000001b9fba972f4f9d23dc9571ba130a362f6faa99e435528e9668f71ac2212ee43dec58bd736c2052d2c9fb290edea95f24e754bf06575228240c74d26ad926e4ac42000000000000000000000000000000000000000000000000000000000000001c41989d0afb64e520c01fefda483cb8ff4f53e7ef0716b79192da8bc5849feda07ac0586bfd0da99484761e6a97d2f23bc33dba078c4afd9931cd638ee8eabfdd000000000000000000000000000000000000000000000000000000000000001c890662929abf74b8ccbc04aa1840494feb16da286cd67a76f7d9b5af8e0647e1546a8a07a43c04075dd1d09dc546697eb577456a9e3c86480180804b57169eb4000000000000000000000000000000000000000000000000000000000000001c8d12cef735d590769d70ceeedc93ac7a5e7b0235a66105ff45737df5aa1d0a3801d4ffffd84493275f8ae3322184a147e673bb38df67c5f6ea46d541847b51ba000000000000000000000000000000000000000000000000000000000000001c3fc09d188ee57e747af80ab904b658f36199f409277274982a2c8030545d5bb51174a586285f2251fbba3c57edfbad15bafe7d6eb6866e2b8d97d447c30dac89000000000000000000000000000000000000000000000000000000000000001c2cbdd934a5bbf0feaf41afee32d63a69f103ecea815a3aa4005f6465aff1b3080ba6251610984974beea2e39bf5f7515be597c409daf37708464b502d91f9186000000000000000000000000000000000000000000000000000000000000001beb95998f0b335f9dc6f40a5baa806e54801dc4eaca1da32baaa54752c05be793201b6b1c8c2eb73d21090aaf8df80311b87f859f4adc508ba98ec0c0fc35f6f8000000000000000000000000000000000000000000000000000000000000001cef2d757dfa9d8502ad8406631252377f3151a9b77bc0149aa8093dcef95ca61716da4a8d032e789fa74e98a2242e19d616a391b7022d0e1d01ef4a73b890ca35000000000000000000000000000000000000000000000000000000000000001b230680ffb67accfbfc6ce70b5c570cce21c5505d8a9e3305acc8f045a08c208b224894497429b6ccc1e174bc60223a8bc62604f44fe52ea44e98ce7e5601461a \ No newline at end of file +0xd7d3c450000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000001060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d300000000000000000000000092561f28ec438ee9831d00d1d59fbdc981b762b20000000000000000000000002ffd013aaa7b5a7da93336c2251075202b33fb2b0000000000000000000000009fc9c2dfba3b6cf204c37a5f690619772b926e39000000000000000000000000fbc51a9582d031f2ceaad3959256596c5d3a546800000000000000000000000084fae3d3cba24a97817b2a18c2421d462dbbce9f000000000000000000000000fa3bdc8709226da0da13a4d904c8b66f16c3c8ba0000000000000000000000006c365935ca8710200c7595f0a72eb6023a7706cd000000000000000000000000d7de703d9bbc4602242d0f3149e5ffcd30eb3adf000000000000000000000000532792b73c0c6e7565912e7039c59986f7e1dd1f000000000000000000000000ea960515f8b4c237730f028cbacf0a28e7f45de00000000000000000000000003d91185a02774c70287f6c74dd26d13dfb58ff160000000000000000000000005585738127d12542a8fd6c71c19d2e4cecdab08a0000000000000000000000000e0b5a3f244686cf9e7811754379b9114d42f78b000000000000000000000000704cf59b16fd50efd575342b46ce9c5e07076a4a0000000000000000000000000a057a7172d0466aef80976d7e8c80647dfd35e300000000000000000000000068dfc526037e9030c8f813d014919cc89e7d4d7400000000000000000000000026c43a1d431a4e5ee86cd55ed7ef9edf3641e901000000000000000000000000cb9791b433028b8af808afae87a5c462b19bdc5c000000000000000000000000de7a54a8f8a7b38abd82ea37a0b16a40ccc2a88d000000000000000000000000387f1112a41afe50f3ce5994aca73d74b955be2d00000000000000000000000092a1cdddc0318a9b5dcbe461fd8a5db1ac710cee0000000000000000000000006caf3c6ca0886f2eabbd0db9affb4dffcd13d7c7000000000000000000000000679a451be81dd0900b67a0cbff83ce916ee225c00000000000000000000000008f67cba9fe1963d10292af8a1cb55c2218dd2ee6000000000000000000000000cc5e7d91cc4ac0c5d2f5faca979d656836addf92000000000000000000000000b0238677d6943855295bb9b6bf02f3990b8f26910000000000000000000000003240a90b048a226e6e9a4be508ccdf1af0093e8f000000000000000000000000036883cefb101a5b2fb9d04a0f7bd79b30eedecf00000000000000000000000093850baa3b36c5f38259dbcb1dfe41a51f2220c9000000000000000000000000ca049f8d721e9e7678780de0b9a10bbf35484725000000000000000000000000d01187d2f34019dd46e620972a7856070e59798800000000000000000000000079f00c728e2e1b04654d7f29644e0f65c4d4e60600000000000000000000000058db31c219fb60754106eb39f01113420922b3ee000000000000000000000000db199b549f079634353f4ed311fbd618d51f7151000000000000000000000000a8585cb41afac4acc564b1340cbc056b001a206c000000000000000000000000aa43af6ec4a697cb9080701b2ecefbe6a55e5c06000000000000000000000000f0ef51ed1e29ac1b9e9223fb48055d9c4dfb6cc6000000000000000000000000a577c2e6df1c5ace2f196616b0b4f57a5a5a3020000000000000000000000000b7b2484267460bfe170e6ea9479e10191563b11c0000000000000000000000009aed48caadcf32640a97cc6c20429f306086142e000000000000000000000000543d3c730079576c316558bfce1261e03bca04ba00000000000000000000000082deef4e2a7eefe4372876681057265a9fec449f000000000000000000000000a740131837b6d9047b04639ea88b9a7b35e5ed48000000000000000000000000732f3a6b3ca0f434fb2acda13947ff76a485424500000000000000000000000027556e6d4884684bb38d315544625c4a86af599f00000000000000000000000090f3bd80ccdf4cc8ab6609b20a221374430f90ef0000000000000000000000008d778b8365cea102ba549d5d7df0841ad7d67d77000000000000000000000000752940f08d52a4d7d8563ad3b2d8705c1684ac4b000000000000000000000000302674a7d30fddd4429d5fc0f59a986d08e7a2b9000000000000000000000000ce2fa55f119bfba1930f8a715d9d3e9cba00f97c000000000000000000000000e3df710194d6a63f3f75122bacb8ea6709b96e790000000000000000000000006e26fd8e46d3f74d16ef95ba43bdd556270069eb000000000000000000000000b65ed96ae0f71d1f1d05d8dc5dc0f9579f2c2d01000000000000000000000000e2c9a10c28624175c833f51da5e23af6d5f16f79000000000000000000000000e04c01bf8ab45e7e6bc08bda492bff731cb03cd8000000000000000000000000b68bd07399080046b3bca6292c509750de2ba363000000000000000000000000b79d1319fc753aae93a9777303134df617ec49fd0000000000000000000000006aa97edf1dcb23d50f0443366f4299f887cfae9a000000000000000000000000ca9f8aebac39edde761f5fd369f8ade1c8237f32000000000000000000000000a1e5f8f420a569b8f522895fed1bd80a4e4b43170000000000000000000000003e1fad1561aa55fde0631bdcabb9d318e66dbbf80000000000000000000000008cccf9542187d8d01ea8cdd7707e164016bf465900000000000000000000000005365245fd9bd47f1219ef8246636b2cc9253f4a0000000000000000000000002609a961883af111a04378356991f333f76791a6000000000000000000000000c1144baac2b15745f289ab6eee2f66afd2a478350000000000000000000000002a71999f4128bec31853131af1c7046a20797c730000000000000000000000003f3c44b845a1e877bedff9c88b6d7128d507d6ae0000000000000000000000009b79cd118008e3c15c91ef580fb27192abccb16300000000000000000000000001ec4766fcdd477ddf260171f1f7aab30aaff75a00000000000000000000000006cfb0368c9b66402174b406aebdbe075f5915c30000000000000000000000001f850a925a102f40aba92ad3ee5f30291d5a260b000000000000000000000000c8b7d081a9d6b7ccbda1af96a3700746f39839ae000000000000000000000000cc8984a17735454c53c574f9033f7a703f425048000000000000000000000000ad3f4ec8d7dc7cc1a37e049f68b21787f5706a72000000000000000000000000d5745b8283b47c26aba350c260b1221b62c683df000000000000000000000000f2f635f34bc373e97f778a4e9f7b082e64ad2f24000000000000000000000000884b95bcf31ac67fc07a2ca38ac432ac3dd623ec000000000000000000000000fa2a5fd9ac9c8e6160a84e3b177b378a853396b6000000000000000000000000a7f68b484c5050f971a8a360b5b44d6453be2c83000000000000000000000000ca4a4009ac47375177cbae20965f5d3eafd7f394000000000000000000000000610c239140f02a73d92bbec930c58b48359163dc00000000000000000000000028af19a1a3979182057dc0d5193bd852fd5884790000000000000000000000004c49672ccae0316b88b2fa5cca8296eda5c6c035000000000000000000000000279cbbd20a8fd28cba6a7a0e288e44cd6723030600000000000000000000000091ccc56bc81210c5ae58737377dbf7df61ac62bd0000000000000000000000008411a933ef1df8bd6b9b4ede1eb313b764bf3069000000000000000000000000e2b5b093993f05f5def6306a127f07aea15da1e90000000000000000000000003bd55f92454ffb7e74064e674e3310849b08022f000000000000000000000000aa1b1e373eb51b4b46ed0725f951f45a060aa6fa000000000000000000000000048f831a1d305899696833e43a277fe51ba4c422000000000000000000000000531ff58c527fa35a3a69ba44e11a56376f1d4e52000000000000000000000000a68b7db9de29c71d76045ecd1df336d9a18b610d00000000000000000000000002f232708bae9418fa18406aa896b0d75f8bfeb4000000000000000000000000d70769cafa4bc86365eee2549d48e9cb21dbbac4000000000000000000000000ba7cfb373b94afa6b1342ceed09dcbe474200231000000000000000000000000a95ce833e0c71fe1f690a2408866788479d77421000000000000000000000000a55f348325a9a0eacf71e98aedfcadd4d0afe5860000000000000000000000006b2a7799bfb2636b3246a79a36300092df6e8a5f0000000000000000000000008827026627924a8ca5e37d2364f054e0276bddf6000000000000000000000000eddc8e20b44bc2313117a0befb76086b1159f808000000000000000000000000830a8f97bba7c7558ae9045414407e00733bb627000000000000000000000000bfe43ba10f238b6a50923dd7e77114fe820d3eeb0000000000000000000000004cba62556364705480679aea0f49a3ad5dd14902000000000000000000000000a5be76c9b8416f93b23251279a34bb1b79adfc03000000000000000000000000bd5d7df0349ff9671e36ec5545e849cbb93ac7fa0000000000000000000000001786e3fa4005195cb2e2e7f1e53bb064b22a6f91000000000000000000000000104f3d52690f941999bf8a2f0e940233a6b08c96000000000000000000000000f53aed24fbaa33c1c8a7fa06e447ef042bd8cbbd000000000000000000000000fd012bdc1b9759dfb16ff66db6bce35a03fc1e8b0000000000000000000000003de084e4dab811bb28772d62a7f01ec075a8f394000000000000000000000000d5968a370ebf06a953096c2ff8866f2726b89ba30000000000000000000000003b7aaf86641c4834fc8356308be56e5760e4adae000000000000000000000000822dd57f3f921c40d907e14301824055b8b74df600000000000000000000000034c0efce1207da82659ce0ce0aaf06946aa7ab2a000000000000000000000000d75d1629c726756216b663c5507e88d7441a986b000000000000000000000000b00814592d667c8bf24d47de7d7c013dc83bebfc000000000000000000000000ba4f4ef59261245852fbedda5596775d374495cc00000000000000000000000085d360f5ea6ec190c623ff522a8c1e48f903a89c0000000000000000000000003ab0add61c58354c5b111f5ddb2dbce124b15bc30000000000000000000000005e1cdd3069dcbaac9ef9047dcbdce5e589c36c1a0000000000000000000000009b0e45b85167a7fb6b4d53a9f7abaf3d0d2febe70000000000000000000000007032521a461954bc6bcadb6948133020032786c3000000000000000000000000000000000000000000000000000000000000007d00000000000000000000000000000000000000000000000000000000121965e8000000000000000000000000000000000000000000000000000000000fe5c9c8000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000b3d083f000000000000000000000000000000000000000000000000000000000a8c15bc000000000000000000000000000000000000000000000000000000000a6b511f00000000000000000000000000000000000000000000000000000000082a99870000000000000000000000000000000000000000000000000000000007fcb9780000000000000000000000000000000000000000000000000000000007f62bbf0000000000000000000000000000000000000000000000000000000007c1bdf700000000000000000000000000000000000000000000000000000000064c2fc600000000000000000000000000000000000000000000000000000000062b6b290000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000573eaed0000000000000000000000000000000000000000000000000000000004f7663200000000000000000000000000000000000000000000000000000000047ae17700000000000000000000000000000000000000000000000000000000046dc60500000000000000000000000000000000000000000000000000000000045a1cda0000000000000000000000000000000000000000000000000000000003f1414a0000000000000000000000000000000000000000000000000000000003d07cad00000000000000000000000000000000000000000000000000000000038ef37300000000000000000000000000000000000000000000000000000000032617e300000000000000000000000000000000000000000000000000000000030be0ff0000000000000000000000000000000000000000000000000000000002e48ea90000000000000000000000000000000000000000000000000000000002d0e57e0000000000000000000000000000000000000000000000000000000002bd3c530000000000000000000000000000000000000000000000000000000002b020e10000000000000000000000000000000000000000000000000000000002a9932800000000000000000000000000000000000000000000000000000000025aee7c00000000000000000000000000000000000000000000000000000000024dd30a000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b79800000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022680b400000000000000000000000000000000000000000000000000000000021965420000000000000000000000000000000000000000000000000000000001cac0960000000000000000000000000000000000000000000000000000000001b7176b0000000000000000000000000000000000000000000000000000000001a36e40000000000000000000000000000000000000000000000000000000000182a9a300000000000000000000000000000000000000000000000000000000017c1bea00000000000000000000000000000000000000000000000000000000016f007800000000000000000000000000000000000000000000000000000000016872bf000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000147ae2200000000000000000000000000000000000000000000000000000000014120690000000000000000000000000000000000000000000000000000000001205bcc000000000000000000000000000000000000000000000000000000000113405a00000000000000000000000000000000000000000000000000000000010624e80000000000000000000000000000000000000000000000000000000000ff972f0000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000ebee040000000000000000000000000000000000000000000000000000000000e5604b0000000000000000000000000000000000000000000000000000000000ded2920000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d1b7200000000000000000000000000000000000000000000000000000000000cb29670000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000be0df50000000000000000000000000000000000000000000000000000000000b7803c0000000000000000000000000000000000000000000000000000000000b0f2830000000000000000000000000000000000000000000000000000000000aa64ca0000000000000000000000000000000000000000000000000000000000a3d71100000000000000000000000000000000000000000000000000000000009d4958000000000000000000000000000000000000000000000000000000000096bb9f000000000000000000000000000000000000000000000000000000000096bb9f0000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de6000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000083127400000000000000000000000000000000000000000000000000000000007c84bb000000000000000000000000000000000000000000000000000000000075f70200000000000000000000000000000000000000000000000000000000006f6949000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000005bc01e00000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000553265000000000000000000000000000000000000000000000000000000000055326500000000000000000000000000000000000000000000000000000000004816f3000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a00000000000000000000000000000000000000000000000000000000003afb810000000000000000000000000000000000000000000000000000000000346dc80000000000000000000000000000000000000000000000000000000000346dc800000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002752560000000000000000000000000000000000000000000000000000000000275256000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d00000000000000000000000000000000000000000000000000000000001a36e400000000000000000000000000000000000000000000000000000000001a36e4000000000000000000000000000000000000000000000000000000000013a92b00000000000000000000000000000000000000000000000000000000000d1b720000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000001060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d300000000000000000000000092561f28ec438ee9831d00d1d59fbdc981b762b20000000000000000000000002ffd013aaa7b5a7da93336c2251075202b33fb2b0000000000000000000000009fc9c2dfba3b6cf204c37a5f690619772b926e39000000000000000000000000fbc51a9582d031f2ceaad3959256596c5d3a546800000000000000000000000084fae3d3cba24a97817b2a18c2421d462dbbce9f000000000000000000000000fa3bdc8709226da0da13a4d904c8b66f16c3c8ba0000000000000000000000006c365935ca8710200c7595f0a72eb6023a7706cd000000000000000000000000d7de703d9bbc4602242d0f3149e5ffcd30eb3adf000000000000000000000000532792b73c0c6e7565912e7039c59986f7e1dd1f000000000000000000000000ea960515f8b4c237730f028cbacf0a28e7f45de00000000000000000000000003d91185a02774c70287f6c74dd26d13dfb58ff160000000000000000000000005585738127d12542a8fd6c71c19d2e4cecdab08a0000000000000000000000000e0b5a3f244686cf9e7811754379b9114d42f78b000000000000000000000000704cf59b16fd50efd575342b46ce9c5e07076a4a0000000000000000000000000a057a7172d0466aef80976d7e8c80647dfd35e300000000000000000000000068dfc526037e9030c8f813d014919cc89e7d4d7400000000000000000000000026c43a1d431a4e5ee86cd55ed7ef9edf3641e901000000000000000000000000cb9791b433028b8af808afae87a5c462b19bdc5c000000000000000000000000de7a54a8f8a7b38abd82ea37a0b16a40ccc2a88d000000000000000000000000387f1112a41afe50f3ce5994aca73d74b955be2d00000000000000000000000092a1cdddc0318a9b5dcbe461fd8a5db1ac710cee0000000000000000000000006caf3c6ca0886f2eabbd0db9affb4dffcd13d7c7000000000000000000000000679a451be81dd0900b67a0cbff83ce916ee225c00000000000000000000000008f67cba9fe1963d10292af8a1cb55c2218dd2ee6000000000000000000000000cc5e7d91cc4ac0c5d2f5faca979d656836addf92000000000000000000000000b0238677d6943855295bb9b6bf02f3990b8f26910000000000000000000000003240a90b048a226e6e9a4be508ccdf1af0093e8f000000000000000000000000036883cefb101a5b2fb9d04a0f7bd79b30eedecf00000000000000000000000093850baa3b36c5f38259dbcb1dfe41a51f2220c9000000000000000000000000ca049f8d721e9e7678780de0b9a10bbf35484725000000000000000000000000d01187d2f34019dd46e620972a7856070e59798800000000000000000000000079f00c728e2e1b04654d7f29644e0f65c4d4e60600000000000000000000000058db31c219fb60754106eb39f01113420922b3ee000000000000000000000000db199b549f079634353f4ed311fbd618d51f7151000000000000000000000000a8585cb41afac4acc564b1340cbc056b001a206c000000000000000000000000aa43af6ec4a697cb9080701b2ecefbe6a55e5c06000000000000000000000000f0ef51ed1e29ac1b9e9223fb48055d9c4dfb6cc6000000000000000000000000a577c2e6df1c5ace2f196616b0b4f57a5a5a3020000000000000000000000000b7b2484267460bfe170e6ea9479e10191563b11c0000000000000000000000009aed48caadcf32640a97cc6c20429f306086142e000000000000000000000000543d3c730079576c316558bfce1261e03bca04ba00000000000000000000000082deef4e2a7eefe4372876681057265a9fec449f000000000000000000000000a740131837b6d9047b04639ea88b9a7b35e5ed48000000000000000000000000732f3a6b3ca0f434fb2acda13947ff76a485424500000000000000000000000027556e6d4884684bb38d315544625c4a86af599f00000000000000000000000090f3bd80ccdf4cc8ab6609b20a221374430f90ef0000000000000000000000008d778b8365cea102ba549d5d7df0841ad7d67d77000000000000000000000000752940f08d52a4d7d8563ad3b2d8705c1684ac4b000000000000000000000000302674a7d30fddd4429d5fc0f59a986d08e7a2b9000000000000000000000000ce2fa55f119bfba1930f8a715d9d3e9cba00f97c000000000000000000000000e3df710194d6a63f3f75122bacb8ea6709b96e790000000000000000000000006e26fd8e46d3f74d16ef95ba43bdd556270069eb000000000000000000000000b65ed96ae0f71d1f1d05d8dc5dc0f9579f2c2d01000000000000000000000000e2c9a10c28624175c833f51da5e23af6d5f16f79000000000000000000000000e04c01bf8ab45e7e6bc08bda492bff731cb03cd8000000000000000000000000b68bd07399080046b3bca6292c509750de2ba363000000000000000000000000b79d1319fc753aae93a9777303134df617ec49fd0000000000000000000000006aa97edf1dcb23d50f0443366f4299f887cfae9a000000000000000000000000ca9f8aebac39edde761f5fd369f8ade1c8237f32000000000000000000000000a1e5f8f420a569b8f522895fed1bd80a4e4b43170000000000000000000000003e1fad1561aa55fde0631bdcabb9d318e66dbbf80000000000000000000000008cccf9542187d8d01ea8cdd7707e164016bf465900000000000000000000000005365245fd9bd47f1219ef8246636b2cc9253f4a0000000000000000000000002609a961883af111a04378356991f333f76791a6000000000000000000000000c1144baac2b15745f289ab6eee2f66afd2a478350000000000000000000000002a71999f4128bec31853131af1c7046a20797c730000000000000000000000003f3c44b845a1e877bedff9c88b6d7128d507d6ae0000000000000000000000009b79cd118008e3c15c91ef580fb27192abccb16300000000000000000000000001ec4766fcdd477ddf260171f1f7aab30aaff75a00000000000000000000000006cfb0368c9b66402174b406aebdbe075f5915c30000000000000000000000001f850a925a102f40aba92ad3ee5f30291d5a260b000000000000000000000000c8b7d081a9d6b7ccbda1af96a3700746f39839ae000000000000000000000000cc8984a17735454c53c574f9033f7a703f425048000000000000000000000000ad3f4ec8d7dc7cc1a37e049f68b21787f5706a72000000000000000000000000d5745b8283b47c26aba350c260b1221b62c683df000000000000000000000000f2f635f34bc373e97f778a4e9f7b082e64ad2f24000000000000000000000000884b95bcf31ac67fc07a2ca38ac432ac3dd623ec000000000000000000000000fa2a5fd9ac9c8e6160a84e3b177b378a853396b6000000000000000000000000a7f68b484c5050f971a8a360b5b44d6453be2c83000000000000000000000000ca4a4009ac47375177cbae20965f5d3eafd7f394000000000000000000000000610c239140f02a73d92bbec930c58b48359163dc00000000000000000000000028af19a1a3979182057dc0d5193bd852fd5884790000000000000000000000004c49672ccae0316b88b2fa5cca8296eda5c6c035000000000000000000000000279cbbd20a8fd28cba6a7a0e288e44cd6723030600000000000000000000000091ccc56bc81210c5ae58737377dbf7df61ac62bd0000000000000000000000008411a933ef1df8bd6b9b4ede1eb313b764bf3069000000000000000000000000e2b5b093993f05f5def6306a127f07aea15da1e90000000000000000000000003bd55f92454ffb7e74064e674e3310849b08022f000000000000000000000000aa1b1e373eb51b4b46ed0725f951f45a060aa6fa000000000000000000000000048f831a1d305899696833e43a277fe51ba4c422000000000000000000000000531ff58c527fa35a3a69ba44e11a56376f1d4e52000000000000000000000000a68b7db9de29c71d76045ecd1df336d9a18b610d00000000000000000000000002f232708bae9418fa18406aa896b0d75f8bfeb4000000000000000000000000d70769cafa4bc86365eee2549d48e9cb21dbbac4000000000000000000000000ba7cfb373b94afa6b1342ceed09dcbe474200231000000000000000000000000a95ce833e0c71fe1f690a2408866788479d77421000000000000000000000000a55f348325a9a0eacf71e98aedfcadd4d0afe5860000000000000000000000006b2a7799bfb2636b3246a79a36300092df6e8a5f0000000000000000000000008827026627924a8ca5e37d2364f054e0276bddf6000000000000000000000000eddc8e20b44bc2313117a0befb76086b1159f808000000000000000000000000830a8f97bba7c7558ae9045414407e00733bb627000000000000000000000000bfe43ba10f238b6a50923dd7e77114fe820d3eeb0000000000000000000000004cba62556364705480679aea0f49a3ad5dd14902000000000000000000000000a5be76c9b8416f93b23251279a34bb1b79adfc03000000000000000000000000bd5d7df0349ff9671e36ec5545e849cbb93ac7fa0000000000000000000000001786e3fa4005195cb2e2e7f1e53bb064b22a6f91000000000000000000000000104f3d52690f941999bf8a2f0e940233a6b08c96000000000000000000000000f53aed24fbaa33c1c8a7fa06e447ef042bd8cbbd000000000000000000000000fd012bdc1b9759dfb16ff66db6bce35a03fc1e8b0000000000000000000000003de084e4dab811bb28772d62a7f01ec075a8f394000000000000000000000000d5968a370ebf06a953096c2ff8866f2726b89ba30000000000000000000000003b7aaf86641c4834fc8356308be56e5760e4adae000000000000000000000000822dd57f3f921c40d907e14301824055b8b74df600000000000000000000000034c0efce1207da82659ce0ce0aaf06946aa7ab2a000000000000000000000000d75d1629c726756216b663c5507e88d7441a986b000000000000000000000000b00814592d667c8bf24d47de7d7c013dc83bebfc000000000000000000000000ba4f4ef59261245852fbedda5596775d374495cc00000000000000000000000085d360f5ea6ec190c623ff522a8c1e48f903a89c0000000000000000000000003ab0add61c58354c5b111f5ddb2dbce124b15bc30000000000000000000000005e1cdd3069dcbaac9ef9047dcbdce5e589c36c1a0000000000000000000000009b0e45b85167a7fb6b4d53a9f7abaf3d0d2febe70000000000000000000000007032521a461954bc6bcadb6948133020032786c3000000000000000000000000000000000000000000000000000000000000007d00000000000000000000000000000000000000000000000000000000121965eb000000000000000000000000000000000000000000000000000000000fe5c9c5000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000b3d083f000000000000000000000000000000000000000000000000000000000a8c15bc000000000000000000000000000000000000000000000000000000000a6b511f00000000000000000000000000000000000000000000000000000000082a99870000000000000000000000000000000000000000000000000000000007fcb9780000000000000000000000000000000000000000000000000000000007f62bbf0000000000000000000000000000000000000000000000000000000007c1bdf700000000000000000000000000000000000000000000000000000000064c2fc600000000000000000000000000000000000000000000000000000000062b6b290000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000573eaed0000000000000000000000000000000000000000000000000000000004f7663200000000000000000000000000000000000000000000000000000000047ae17700000000000000000000000000000000000000000000000000000000046dc60500000000000000000000000000000000000000000000000000000000045a1cda0000000000000000000000000000000000000000000000000000000003f1414a0000000000000000000000000000000000000000000000000000000003d07cad00000000000000000000000000000000000000000000000000000000038ef37300000000000000000000000000000000000000000000000000000000032617e300000000000000000000000000000000000000000000000000000000030be0ff0000000000000000000000000000000000000000000000000000000002e48ea90000000000000000000000000000000000000000000000000000000002d0e57e0000000000000000000000000000000000000000000000000000000002bd3c530000000000000000000000000000000000000000000000000000000002b020e10000000000000000000000000000000000000000000000000000000002a9932800000000000000000000000000000000000000000000000000000000025aee7c00000000000000000000000000000000000000000000000000000000024dd30a000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b798000000000000000000000000000000000000000000000000000000000240b79800000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022d0e6d00000000000000000000000000000000000000000000000000000000022680b400000000000000000000000000000000000000000000000000000000021965420000000000000000000000000000000000000000000000000000000001cac0960000000000000000000000000000000000000000000000000000000001b7176b0000000000000000000000000000000000000000000000000000000001a36e40000000000000000000000000000000000000000000000000000000000182a9a300000000000000000000000000000000000000000000000000000000017c1bea00000000000000000000000000000000000000000000000000000000016f007800000000000000000000000000000000000000000000000000000000016872bf000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000154c994000000000000000000000000000000000000000000000000000000000147ae2200000000000000000000000000000000000000000000000000000000014120690000000000000000000000000000000000000000000000000000000001205bcc000000000000000000000000000000000000000000000000000000000113405a00000000000000000000000000000000000000000000000000000000010624e80000000000000000000000000000000000000000000000000000000000ff972f0000000000000000000000000000000000000000000000000000000000f909760000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000f27bbd0000000000000000000000000000000000000000000000000000000000ebee040000000000000000000000000000000000000000000000000000000000e5604b0000000000000000000000000000000000000000000000000000000000ded2920000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d844d90000000000000000000000000000000000000000000000000000000000d1b7200000000000000000000000000000000000000000000000000000000000cb29670000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000c49bae0000000000000000000000000000000000000000000000000000000000be0df50000000000000000000000000000000000000000000000000000000000b7803c0000000000000000000000000000000000000000000000000000000000b0f2830000000000000000000000000000000000000000000000000000000000aa64ca0000000000000000000000000000000000000000000000000000000000a3d71100000000000000000000000000000000000000000000000000000000009d4958000000000000000000000000000000000000000000000000000000000096bb9f000000000000000000000000000000000000000000000000000000000096bb9f0000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de60000000000000000000000000000000000000000000000000000000000902de6000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000089a02d000000000000000000000000000000000000000000000000000000000083127400000000000000000000000000000000000000000000000000000000007c84bb000000000000000000000000000000000000000000000000000000000075f70200000000000000000000000000000000000000000000000000000000006f6949000000000000000000000000000000000000000000000000000000000068db9000000000000000000000000000000000000000000000000000000000005bc01e00000000000000000000000000000000000000000000000000000000005bc01e0000000000000000000000000000000000000000000000000000000000553265000000000000000000000000000000000000000000000000000000000055326500000000000000000000000000000000000000000000000000000000004816f3000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a000000000000000000000000000000000000000000000000000000000041893a00000000000000000000000000000000000000000000000000000000003afb810000000000000000000000000000000000000000000000000000000000346dc80000000000000000000000000000000000000000000000000000000000346dc800000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002de00f00000000000000000000000000000000000000000000000000000000002752560000000000000000000000000000000000000000000000000000000000275256000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d000000000000000000000000000000000000000000000000000000000020c49d00000000000000000000000000000000000000000000000000000000001a36e400000000000000000000000000000000000000000000000000000000001a36e4000000000000000000000000000000000000000000000000000000000013a92b00000000000000000000000000000000000000000000000000000000000d1b720000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000068db90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007d000000000000000000000000000000000000000000000000000000000000001b0758a1ac2dae8f35aaad1ce9cdf54b28c07f8f585358fecdf0f2a0b1bb8a0bb33873a33765a98720bee32e2ce096392dbc782fd732b46ec04b0f2fac73b132e1000000000000000000000000000000000000000000000000000000000000001b1cad2fb242dadd26dc697bb29af23305e2e6fd098980c98986b15d4583a50abb2933a04fd2b756eeeebc1a6e19addae5060d9f95181bbd829f433c4ac3c0a8ab000000000000000000000000000000000000000000000000000000000000001cd54caa8cdd7143a0fe7de4217034989ead28a85a0381ec88b5632a54f784977704a372faec157f6c46b815a049637522d14ddaa4d1d465d54a236f604f7393d2000000000000000000000000000000000000000000000000000000000000001c9e3b110f9eb385ee72f30ae46d67764ca73a808302ded553289ebdc355f1cff0401d07aa68dbe9d825b06d6d4ca3112ff86fcae29e502e4ffe1f642f1b7d1ef5000000000000000000000000000000000000000000000000000000000000001b92f4feb337673c1a677eba9df70e2d1e2b5230ce54db9d6d9df8b371fa96e5e531420ecbc73b9190f29e442ba973c3e0c795673fa793166f72bc340d4e1a9ca5000000000000000000000000000000000000000000000000000000000000001bdb0def052f0f91b46d38638f9081eb2a395a9717e4b951e60a3f09de3a24d8e4590bca5d3eeaf7b03679ca73036d159ee316a1aa9bb0fbc7307db6911391b64a000000000000000000000000000000000000000000000000000000000000001b71478c4f016d44a26884113671092323cdcab54026ae1e44ae7836a3a87d670d1734d1e9ce2311342ef237d2b913bf689ac050374b864a89cdfd26e30ec90bb2000000000000000000000000000000000000000000000000000000000000001b3963fc10ddd8136ce91c9fca9eeec2362b957777d7d7478fdc7ba20f67e581084ce5738db73fb6659ab128ea8329cd80758f21cc02066216cd90de375836ce7f000000000000000000000000000000000000000000000000000000000000001be02ba1f15875684714a6e42ace738d80cff2b96ac3b6988fcfabdaf0e1c7b2cf37ff1d22717a5ca8079e8d3630788671e43613f0ad1c355d066cbea3e9dce4c7000000000000000000000000000000000000000000000000000000000000001caacdf9c363954eae16e7c79927eb96aca182d4a14b17e01ceab551d24889a0dc373f454d45e7acbc34c4fc2d85e65258cb62daa13e03408fb23f8e9cb2ac577e000000000000000000000000000000000000000000000000000000000000001b56b6210ce5880f7c559a327691210fa2132c5da5ff875a0440b1637402b5d0d34bada169169be8e8b0cb2cc136c66cb7549247eae7d111d1acbf57ca4818947b000000000000000000000000000000000000000000000000000000000000001cf95fdca133c49b705bcf17f898b3a99c271e5d03e58d0ad24d0c9fc61542b57128cd032309f28b45fef8080b445b3a691f910a8c4a7f2c3bdd4f82bb5fc086d3000000000000000000000000000000000000000000000000000000000000001b52f94db682ad8bc3f4d2469ee723b4aa9cabec4077c1d5b50c22b4c18545e3b5569f5510857dfa8abfd44be085512a8b271032bfcbe92f12b146d01f8a2eff93000000000000000000000000000000000000000000000000000000000000001c7d412871a330ea19ad0d663780f37b2d3ecb41022a96621842b7312db6e64fb2563f97b5975ba45f293b2b85bdcd53416fa143d6df368556915f49abbd5350ec000000000000000000000000000000000000000000000000000000000000001bc0a811d0e834b3e7b0d4051e3be27ad5daa66b5fbb9b3a01af2b2e50e62c3e683d4b8b91611c769c115d1a66b0b030bb43635a807255577d62dda5518c8f10ff000000000000000000000000000000000000000000000000000000000000001baa4f19322afaf5585ef9cbb5409ce5ff229bc65cc13360a0c03137a12f8d1fda3c09547fd1469edd765e0f90f27499b570c1d6df20fed704950e222d9fcbe9a4000000000000000000000000000000000000000000000000000000000000001c704ecebeba31c2e83cce0cf1f707dd774831840c4e1efe4f26700b1571b1f188096feb8cbb18fcedd02570ff17136025010cc018e763e4172433a8374aa464ff000000000000000000000000000000000000000000000000000000000000001c3575e7232b7b4548904af3c085fb68fdde88746051ff91a0ae40a5225ce28b3f7df7f41ae32cbdbbc55bf11b9fea3c189b18b82155d35fd1c42ac1c7c061c627000000000000000000000000000000000000000000000000000000000000001be5df308e09ba2c14b2ebd2e7f729f423a4ac1684127eece91ed01f20540e70d00fe73cbccfdc949ae426747e301445b45a4fe9be016ddfd002eaa9077e2510b1000000000000000000000000000000000000000000000000000000000000001cbfb9287c8d6d5292a9f45c3b83e5010b73524bff2f5a656f0b4dbed9dad595230d13cfc99897f1ccb6b9c7051444c75d8a47fdcfb20175ce0f45d13b99816966000000000000000000000000000000000000000000000000000000000000001be1ed3c6381bad1a017adc2bc621ebc7a5d83fbc7efc7c60d4d442b24ad15653466d5b9dea0dc8c4af0530675ac0ad89421267faee37450d5fc1a1b04dd7dc8f0000000000000000000000000000000000000000000000000000000000000001c91437e5cabc751674ba5babc8c7b410befb4ea00ac86fbe2d5846c9af8382da54b3e3d7371f4598201596c569c15d9af401d67861edb7a634a0cb5b70d9dc7b9000000000000000000000000000000000000000000000000000000000000001c8e840e5030556340a701dccbf9f02b782432b782fb32b555473c39d4f23d402758ef298f347d353818e9cb1d8a0d7f4800288e16314e2ef3ef67aef57db902f5000000000000000000000000000000000000000000000000000000000000001ceed525b2f3063279a128fd2a0aed4cc88b7e42909355b3aea81a20ab12651a3b276193f276fa25ad6f42d5d8fb1a6e3a4dd2b293032c37a279ed3c0f704a8ccf000000000000000000000000000000000000000000000000000000000000001c6e083dd5427a287ad51be51a1a8e1d97068c407c6b3361d5cc0b94bc84d2f5e41cbef27bfd543cd571c1b0dab22f5b2b7528fb2c9662bfe15da57017a70a680a000000000000000000000000000000000000000000000000000000000000001bf563ff7f3351348b8f9d76a47652512228684e39a77e523ca69585d77c2a6a623bd4dc78eb669a47509f5ff80232b318633ef5948551d50fbe6a2996b76b0046000000000000000000000000000000000000000000000000000000000000001c02d30b6cfdf8301ea85b4c297bbc0c12e10e86c700a4fc0ee63890a17849dc9b7d0f05c5d534ebd9a062312068218490a21378171a0db9158e89941cf2d03f25000000000000000000000000000000000000000000000000000000000000001bb54a5cc64f65eff0d08308d35b8ae9a25fa4902dc5bf830d10f97a7edc1d326f6254e253c547af10819d6e78b13582955f15ccd4385cbfe91a34b998eb9609c8000000000000000000000000000000000000000000000000000000000000001c331e9accba8820af7c33d89c31d9d3d53dac6fbeecf418aa1ad88cbe6eda42a220c134e8725bd2ae47fdac359e96aec9df38b72363afc318d88d19c4d367cd4c000000000000000000000000000000000000000000000000000000000000001bf80977ff9f59a7db2a6bd8fd5c48808da71869eb453eb492ce8c0385c218192155c93e0f2d0987eeaabcb7c308546271010eb104e75e56107c16b5f665eaced5000000000000000000000000000000000000000000000000000000000000001c361b9d0da617dacfd29b6274a904be5ff4da8b955fdf0b580d5d4b9555794e9b6af1cf7e5f931c5ab725ea986d8fe46f9557469496379c93fe44af6668056098000000000000000000000000000000000000000000000000000000000000001c3a007ff1ef7346a1a2258eef996d9858f1c2ed73ad04da3fbc84583a82ce4537610acc32f8577831d097a0354a3018d3392ac4db04b2dd5a8da6d47132475caa000000000000000000000000000000000000000000000000000000000000001c81ce3cee3309172f215eed2b1353222cf0e3f57ba33407803c8a365a591b08bd127e7bff7e6212046fde0a9c9e2194208f8df16566c027d7cf971db6ec7cd0bb000000000000000000000000000000000000000000000000000000000000001bf69a3bd6bf97b11c14999bb5adb2accff74ed49271adc08ffff24edaf20c42e8119b55636a97b57e2018cbc8466d5206ed56b70d4f85cfc949c4fb6e15e0e7b4000000000000000000000000000000000000000000000000000000000000001c1f4a1800559963f8afd1b6d2867c9e91ff037682325bf335fb0cfab72865155b513bc9f90affa6f96922fc6cc1940c1f0135378a6065e44b80092eeceb1de263000000000000000000000000000000000000000000000000000000000000001b92063c7503347e9e4ef57ee82eb0d71b58215c0af7faa9f4fbf1c6ca7bbe6c4f6ec355e68634b90e6bc1463778a1ae283aa57a0784c8133dadebf5f28ded85ee000000000000000000000000000000000000000000000000000000000000001c78e73f30ba08674e27c3fd2568c5e009ac8af90b2df5515eeed8e935c70d63b234bb97991411ee8cb49a58e77bc3ae3569866dfb79d66a1433af1311b0973de9000000000000000000000000000000000000000000000000000000000000001b4db1a8da8ccef73ec8b7d4eb407e7ccbc6a3b65fc30d87148326c235bdea795145547122bd22228ad4a42e09619acd6148ae62c3ff14b1a70a31ebd189ecd04f000000000000000000000000000000000000000000000000000000000000001b6fb96a822d492ea93d9eaa78516ad4435a21dd4e74230809d429c2cfd8ecc77f1ca6adacd5eab55d629d87210f8316e31a2a374c12203d68f0e63b9558f55de5000000000000000000000000000000000000000000000000000000000000001c18525143c2ed4347068381291a4f86a825ed4bd5d21677ff3018d686674fa17773be2a017edfed656e3dcba0fd2aa9faf45a01d934bc8aad250f68112f9dcde9000000000000000000000000000000000000000000000000000000000000001c46c40c7375b78e04f349850ff1bda10554960b2d25c0490f7b9e85f20ad45b5a0216faada1e3dc32ce2c443f5cf6ca2fb15444ae7629b4ce3d26e92637f045c7000000000000000000000000000000000000000000000000000000000000001c84456394dddab3c2c892a36e1612fc22c9e829e84c5122fad49465e3d4062c191c1da84c64c768d069fd502f6a08d4bb9ffee324f69688327abb4d2d593448a1000000000000000000000000000000000000000000000000000000000000001c5dc5e9d5293d10bc529c63a0c48839110ce3bbfd90000a3f7b3ae0f0b2f3498f7188d199139911d2e47d828a91d2f46e63947fe3128a3dfb15b53ca865575e27000000000000000000000000000000000000000000000000000000000000001caf3e788a024d7474129b62ef59a2fec644846dfbacc65a8838698a111ad71fee22af14f68fcaa2e21e6f86ddbc05dea8a05470cc4de306f2e74735b42933837a000000000000000000000000000000000000000000000000000000000000001c6e3f6571f34cad0d6c1b1e844b12cbc125bcf95b4c42f8a91b855c1f5554a2b52e289f07df7d8ebd9220235f52b1282c37935e23f15f98b8f076c8a4b57c0d08000000000000000000000000000000000000000000000000000000000000001c03f1063038f70a557a1ab51fd62512d17536b06ae520a9bdac5ab0bf50b2f3d72fb81604d92dd35e4445580e0454031825230c2b6c8d67f17370d17c0c117ffe000000000000000000000000000000000000000000000000000000000000001c8fddabfb76cbfa09d8d7255ac9789205b94415794cf124970e0ad2e8885aa4010106533298e52baa5fb3588ce12d7bb1e17b01a302e1aaecbfb95edfbe855bf8000000000000000000000000000000000000000000000000000000000000001bb68f01281b4956aa7181fb45b65d3c54adaf5c5c52a8fb5efa0c0b245bb419115eab45a29ff85d67e84aedfe67b0f9033909d017dbd9ee29651fe01c01a91196000000000000000000000000000000000000000000000000000000000000001c93d94e66387a8fb58b29cdbd1728cf14d135b8826ea6f916e82f6e6b09a501e11b5d935c6ce3a3428c7394df4bffc332010d8f184fd45048d74b40f5e9fdb93d000000000000000000000000000000000000000000000000000000000000001bfc42bd6451c6a7bb97d7ff921e2ba2380bce1ce665bb2eb36e5f46d378130578703c2df089552f1c82dcf12155eb258b0248b6d753efc00e4365f018142c43c3000000000000000000000000000000000000000000000000000000000000001b5e1b341a24cbcc8dd4e6b92e36be13a6021555f53f9f7712053d7982818e19a47db3e73a03b4722f7e366f1b49e8d10bb1c8ce6f6ce44a0680f28a9e1de2f34c000000000000000000000000000000000000000000000000000000000000001be56ac5c2838f7e5796545bc88cc7ece8befc1cd39edb039bdcfe8e1b81e2de3830a3df2c0cf8322cc8b26a3ec5a349683285f63e9dfec5da8c5869483ad9f784000000000000000000000000000000000000000000000000000000000000001c3cbb29f79811c895c15d5d6ce05f94ce20527bb8a9c72849e119789baf167ed22d0996e94d6ce7e23ae6bfec33dd727b024043402fde42d827795c81ee402e0e000000000000000000000000000000000000000000000000000000000000001c982bef308342f1736806ccc872c89969430d823b3d38312294d357c952565ad42c5b1e0e428307da0a9fa3e0b69a6325ccffe42ec81aeffe221f555dc0a5acfa000000000000000000000000000000000000000000000000000000000000001c5afe0ed5fc456eb9318cd7289d61b2044edd695704dd541526ac8f2e358b445e3cbe833c73b5cfbdd9eb60805821ea5afd5d6861b9f203b84afe2bbcae5fd08a000000000000000000000000000000000000000000000000000000000000001c430fd9b3bd57e3e23f37f0939830f8cffb999664229c204f265af6c27ba7644361be86437f5deaf7e34baf5b76766e17170bbca94dc0422b8572a7401bb32f63000000000000000000000000000000000000000000000000000000000000001b87ff22c4ff3b2f4dea69c365aab6b65560e644aa2f0fb70a911b9a4ac60179fa31b1b34652a9b8bc652591504f5c47c75a4ebde0d4c09b8cc9e25878d28834d4000000000000000000000000000000000000000000000000000000000000001c162a7bc77496f88959f419b986615ae436282477a2bf5e3d6305285e536f36330515c51c95b954610b506afdcd78e26a481bf477d86f2bd31458845e60b1816c000000000000000000000000000000000000000000000000000000000000001be02257657e20713391cab2b888d5ba04677511ae9e175de0ed21c4df958be2c40178c4775f5252a8111daa8285e16d4e617fcab3bd8db534cd4adc2043d63277000000000000000000000000000000000000000000000000000000000000001bde868dcb96d0c67ca3fa63f95f2d872bb09da93a09a7a042206ec33073912550147c4486a7cdcc461d532571d40f7810d11a658dcf8f8c0ad0eb438cbbb7fb17000000000000000000000000000000000000000000000000000000000000001c875e554ce6659da927c0045838dc326e3cc1459ceb83b2f0d79f3e5f74c950a007287013d0b22e5048be51a602e2f3c21e81ad160bb2b011807323c85bfa43e0000000000000000000000000000000000000000000000000000000000000001ba0f75b10b841da521c78d9deda1e4dd01da7a86209532ee89b84bb194ffc089426611632bd9e26aceb94d304288e9e3d7b14098912130e3027e670d8e355562a000000000000000000000000000000000000000000000000000000000000001c1a56b9ee91215ef0db7085fd72db712f4aea7d331230f236383f04cce928662923deae2bdfcc7c0b86bf7f1c2ddc5591f2839753a427d0aed3570e4e9dc27a24000000000000000000000000000000000000000000000000000000000000001c0e85f1c2e181f761fd593b56a228000823e5ef9871d989ee207cb1834ca7a51215dc266b9f3bc8bcac484dafa50902e6f1f7b9f5e4ccd73f4a25f2ebf3e94f54000000000000000000000000000000000000000000000000000000000000001bb2c1cb2e3544652a15b3ade814db54c035c9a654a10fb4aab10601addcd80e1c4118634b1088d2e235dc76dd42d53f0db015901fdf02e9f4b6820f5d90c8ed39000000000000000000000000000000000000000000000000000000000000001c748af611b79a378812ef30b45f94e01698e146c060719d7ffbbf956107c1df422127dd983142ab7b67ac95062808300cfc36860bd7ef181c592b5d0a06dd8f8c000000000000000000000000000000000000000000000000000000000000001b2499770b9c9ae8588ad03f0040e7444c8a0f7712d51302977ba0300f166a95842a73dcc90b06bcd4df1a075db9549bc3920333b849ab65532b7a6c17ff105b7a000000000000000000000000000000000000000000000000000000000000001b41ee896ab0afaa1fc280052027e4b26a38f32b30f9a72c7245212683ba9b13147810133f6cccb9b039e9c74e6d9e6d89ecbbbb79a0d74a1aab48d9698d1278ca000000000000000000000000000000000000000000000000000000000000001ce66ba8cb6d26da644ecf904585838cb608d021969f0810e3de8b834d3f9688e31dccda662634e0343ced62baf7c80b214b1d1ac487cd2948a1b0b0ad095eb9ff000000000000000000000000000000000000000000000000000000000000001b78b87e68ebc30dc19f09f7ead197eb2b241d7a90b2447cf38caf046940081ae65fc13dd14bf552ac88933d1b01ff8814807a8b68d48d559df9185a4c045503a0000000000000000000000000000000000000000000000000000000000000001ce7f0ce49460a54e768c78f152898916352bd9b6e6f7fd62ad5804a2a65002ec762c17e63ffb8440cb01f6dcfb79620cec9f587939c7d42502f34e87930a9bb68000000000000000000000000000000000000000000000000000000000000001ba3cf956db579b8e0809918fabbfbd907960f780d503e2e95d184dc6309925a757c0375f34b181167eeb0471e92c349f5c286881d5b155efee264371e0d206cfa000000000000000000000000000000000000000000000000000000000000001b42b02ed34071a3e4eaaebd02521259863f5e5277bb1ab709864fc2b77adb477658f6b853ec9a9c019d8c7d0de6b665bb5c7502b547318459886cbe22157375dc000000000000000000000000000000000000000000000000000000000000001c0f27d86eafd2185b80d9774db1268ef2209abe84f32b6f8a427eb7c742fcad8e57029e224122a75d5dd646d1bbeaf89f86adf6fc30fc3ef4e224c80f5501882c000000000000000000000000000000000000000000000000000000000000001b18340d982f8a08d9dd6bd00cd6df2df9f7b2270bb0b92324374d281d470b1b1411692c487b6ff984406ca504e0784b74ef399368e51d271a8dd13d9c521f0468000000000000000000000000000000000000000000000000000000000000001cb0c7507d1de8d9886e8143b5f84387360308d58bf2206d61ec16602533edbac43837d599ffc0b1240de1d4af71cdb65a8ae5cea94a1e728be8c034466bea178a000000000000000000000000000000000000000000000000000000000000001b90821c2aa981309a9f52c3fd3b1bc12b45a2177e7e19c3199ab17130a6da28af24460d6817998a4d9dce4a5820a7cee982702c82ba6a0e59ac5f71d94d8d6cc2000000000000000000000000000000000000000000000000000000000000001bb10b6d15cf74372aa79b5864db88641dd4cc4cc2aebc7344bdf24210b9e894421e340030c514556b019e3bf76b643078fc8c9046e2db44513e7a0df5e711dbd2000000000000000000000000000000000000000000000000000000000000001c4d069312632d6cf997e13055574948d02fe01951a487a62a6467dde63cf455474a5754183f55820ccc68ff04e85a3651e089b9aa78fcbd0226c18143e5415a13000000000000000000000000000000000000000000000000000000000000001b2cc7300fc1033b37b2275950e993608d214a04544e6446cc092b041f1cc1cf315543bb104f31af145001070ead319ed4f0af8baba80025a9e5bd371eb5e4391c000000000000000000000000000000000000000000000000000000000000001c9a826669fc077be3e26a6c33e40a207058892632b300c41743b2c09603758f5530b4ea417e5e29faae2b24fdb3821afce900a3171dfcefc4de4e9eb25fc9789a000000000000000000000000000000000000000000000000000000000000001b11cea389a31678ce5f706350990165781be17d03d8733b8ce155d25f1c284a4261e1698f256e08902a2f73031a9f47d1c672fa060fa380ebc94096dae2dc157a000000000000000000000000000000000000000000000000000000000000001cf28f75e8214400e4f6ca0a528bbb884dcdb501cd8d9caca7d576bd6b892d07015de77138e453ac31124b3968a751783b587c47fd3e5335fd1131d5999208b8f5000000000000000000000000000000000000000000000000000000000000001cc8c601f03481a86b018d6f8edf75065b9e26ec60fe321fb1025f4ede5fe1b21131f55eaf3a331a12d4ac6b35b5927aa379e03975a9f4b1e932551a6408b65563000000000000000000000000000000000000000000000000000000000000001c37bfc814f5430ec116bc494dc43ab1dcf268b9cdd0cb892c840b8f26139f80c60478650096582b60b356b024b312597297b411a85de79daf7564102d9e34393f000000000000000000000000000000000000000000000000000000000000001b954c4fbc1d23970e1b6639d8fa94bc15900903496320278f52b83799efb1a53423e3137de2d4f63d37af419949cc59ee6da9ff2914268a5d01f9fa3696deaf4b000000000000000000000000000000000000000000000000000000000000001ba929fe150944ad6dd20594e50f6bf96b831314b05d4240d78122bf791b4de57b17ce5868e5cbd00ec70b77586a3c2a36913b687a18ec812db69d39399ea3aafd000000000000000000000000000000000000000000000000000000000000001c87fb3215f29f8681bfab902b4d5704fd1ce5320abe049357286dab3a09cfbe525982c5422ceb96c9ea7d04381bdf8d439688422278b15d47159f98caad1f60b9000000000000000000000000000000000000000000000000000000000000001bb53fc129eae21e92f884660b1301cd48f47389983e6c7081f6449b85fee0155b113746d61293082102e35656466b426bf2de715d35288ced430d85d4f8e69a26000000000000000000000000000000000000000000000000000000000000001bcaf71034a6ba2a5aabb3dfc47206da982ac6da61173f3b1917dfc9367447f8e305d20db8e1805fe58e089a3ddee202f8a75b961ae55da3750b2a82797d1a6432000000000000000000000000000000000000000000000000000000000000001c4644bc3e5561c4d96205270387cc43f31f87fb6aec2e47ab41c3dd2cb264f12427f290548c43c115ffbbe92e2540588ba522584b5f47745e8000243a38fd901c000000000000000000000000000000000000000000000000000000000000001b9126e325e3ccf4b3318b825e800d6f25f2a58a81f10f03c9fc7545908f2a93827dfb716a5ccea28f4a21df11495d392c7b19758b0f7d85f38f218561ceece941000000000000000000000000000000000000000000000000000000000000001bf65cfd8f75656a492d81916a3f03fce71e46b23daf8393892ce66560b94473066c104ca744e5106a8293416e406a0d8c9ce0382699d9ba2673c6a1eb95130a76000000000000000000000000000000000000000000000000000000000000001bca47c20ff14a05d48834eba3e88d2b87e2487d84236032a2a21530eacc2ee2fe07732e30ef3afda43d3141c89d5abe3c2d44850dcdc667b858db795aeca68500000000000000000000000000000000000000000000000000000000000000001ca342b456953d5d2c409b4baf5ea4d0621904f3adfdf93aa2e4dd9061c249803432ca99cd015a4f66e7a5703648441b47a929ac2606a01974cd64aa46536da28d000000000000000000000000000000000000000000000000000000000000001ccf6364d8f4b1faa2efdecb05cc3a598429299391a8430a08e25608288ba499506973a76915dd13d28947073a001f246ea605b06bb31d86f3a479ea06094519ea000000000000000000000000000000000000000000000000000000000000001b8e6e100da49ba24d15adae32f4b26fdb358934c1cd02a6308bfe10984819959a6ff6cd42b069ffef3fdc1fb81f23a86aad5c459dda9d96d13c28693a45ca07a7000000000000000000000000000000000000000000000000000000000000001cfb43aa7c6974e827d02fa404efca78281d740a3117801b102759d4f34ee9b9c57e3e40d95551f2b6cd660dae0e6e4ce2e0dd20a27cc172a66a7ec4b4901d8a8f000000000000000000000000000000000000000000000000000000000000001b5d62bde2777a633eef329c1619c4fab3a6da9596fad53c5913ceb0f051bfbb1601201c2279df9717892d8b1192884a9b73f5d21d0e4488686591906fc7fba031000000000000000000000000000000000000000000000000000000000000001ba7f8054a922e1426741c99bee448643af22d2ff095b2603bd2ac49cb3e6efc442f1220ef7b3227e461be667635478d604fc4d0860e7aa93c58119cc5a823c6e7000000000000000000000000000000000000000000000000000000000000001bc52fefbf1781ea268502f37e76af6da9dc3541805bb7f90b72dc6a295b40c9be7e3fe12866912433526e2d85ac98fa159f8c980be8e5b15a84f5b6f9e8316e15000000000000000000000000000000000000000000000000000000000000001cac41a249d8a5a698f240c57cf2317a01bd0051445fc45a090bb57586a0da7b7837435881e2fd971d8d66017a23c71d182b56a23e801f81c369c84cfb5ab40959000000000000000000000000000000000000000000000000000000000000001b2c3098e5974215f64b20dc5b5bc125608841df67a4a4662dc482db2ca805c229257aa789840db0924fc948c419afe2d8ca4f2547ea64e0249adf846a36533302000000000000000000000000000000000000000000000000000000000000001b405b52bd85d7cee0efb48b6186508593d5b83b0fc0073c1de1fb86861ce93cd2509b47b4914f5e8eeb6e311f1d91f807e4218ef099477a0e27cdb500622fbfe6000000000000000000000000000000000000000000000000000000000000001b53b3f46b2ec0bc554467dd3ecf4a8a94ba58a2225aed142cf914873da77e020e2404e6a7cdc7162a5e9c9b3466fc1e4c769a968b8012f536086fdff34245e073000000000000000000000000000000000000000000000000000000000000001bc8d0ac31c20c3c211335ded5f2df8bbf25fc858e514068042e69466b25c8b6177b100597b7d29fb93db542bb6cf2cf960b95c406dfb72fc7ffe6526d9805e290000000000000000000000000000000000000000000000000000000000000001cb7bd774db839f806c9a03c1cbe8e818e4c9b69fda50a0026f846d7a9999e8bb659c9db85ba1f4edce2dffff534909175175e127e5036723a5ba565bf53bcb6b6000000000000000000000000000000000000000000000000000000000000001bc902079e678a6bf6402d7bef6e9804f0c50d6253f545794e7ad99d55be4a3c2a68d5be98ed57231e210dd5dacceb0fe5446007b37ad5843fe7f60a1e5eb7ff82000000000000000000000000000000000000000000000000000000000000001bc02e9ebc994754b89e6cf02ecd55d00bc758b9e919b9553f882aa8e99ef6e3de417e89e4bb93f649968c45a89db679667225a4ee8610b66e328993ac74f08c2f000000000000000000000000000000000000000000000000000000000000001ce68e3939a279439f9948f1339bf3f961c5905b5af61dcaa9cfc2c90b3adc0303488c4713f7b99ed0497e20dc4fc551dd82151cfe59f590ab559cb7b5f54846b6000000000000000000000000000000000000000000000000000000000000001bdae921015427d60d351f53edcab3e63f4d73c3f5ddfcb7e29e940c411d6f051a43ab9320189890f5f86c4e2c0ba197e33a20134a4b14968836f3e6c324a8e727000000000000000000000000000000000000000000000000000000000000001bda51c360e6bbf1f7a75fe78e7a8e1a18f096291e5b5937c973c9c5f41b64ff731d641ede71a8683ad120f6078d4c4b3814df86c5f23106ccf50ea5d48c99427a000000000000000000000000000000000000000000000000000000000000001c285a774b2dce6de7148c6c9d50671bc2bf8dbe903f84370f3da0c1c7f03c78f63ef97d9d80064e8f6a98ddf18424b2aa0365904c8473402e56ab779b946554bd000000000000000000000000000000000000000000000000000000000000001c04598db4251167854863dfa9546afeee050139cd351eb4fb1f2bb1ea92e11008478f592aa760cf1d142b0eea0a7d093b4989804004edb78027d1842e58159396000000000000000000000000000000000000000000000000000000000000001b58a3090db87fbe8e258154ec8e99d9043e1601d0046ac99fcc4d7639c5e297464ed80f5f14091bf5dc048e02b2c1a9e2e323a07ac0798cb9656030e53cb4ac6b000000000000000000000000000000000000000000000000000000000000001b07a73093ce58caeebedc17fae8779f6015d80b503bb514ec46b447ed2dd8a9c56850984990eedc55493754ab3c5bfd333182b61223253bf9a74bf445b7f7b68e000000000000000000000000000000000000000000000000000000000000001ce495395822c0c03b145bcb641344d7d8192f50de2306a97f2901a8af42d021d8228a1fedc304363f06382736fc00e7d2ea6751406fe86f09f2492e8563de2132000000000000000000000000000000000000000000000000000000000000001c6b89cb72d4d63ce4d854c85f2662e5a22993b421a520bf71d6a9307d0d9c81a4516b3d6d0d5eb59aee250590fa852e10d950991d5157e5f37ebceb9e16f2436b000000000000000000000000000000000000000000000000000000000000001c27cd58671e53d383abdc042fe8113304aa9f401c74a20baddbcff8a124d80fc6257c99f613cf82e969d33470e990be058101dbaea108f3f2d7da34cd7f7cde93000000000000000000000000000000000000000000000000000000000000001be5197a0cd63a5428405d2b3f217f542b95c7994021b19a4227f6b3211ca9a4693048cea0d54db28a2e061d813e043c7567d33e90a843a22cd58d7e0d6beedc78000000000000000000000000000000000000000000000000000000000000001c20af7e4716ea9efd5657c2d3eba08bce4f833ae623226989519e3cacef5eb35d10d952265c866b272fef29f8e6f145ae9274926f8a311effc2fbd0a210a70871000000000000000000000000000000000000000000000000000000000000001b40576e7e171ab4b7b405e67db10ec86e161ad2256041994397ed3774a5c420047d6a47a3f437d3535b2daea2674d5d3bd010c94c4c81a78ea73d05df9750057c000000000000000000000000000000000000000000000000000000000000001c01e9bd28a5e3d4569caa911b24521aa368c48898975fa153f7e1bd6fa830c8da3bfecaf87c4a87aead8910f38de9db2728d397b1ff38dea2a0f7c56ce73d48cf000000000000000000000000000000000000000000000000000000000000001c8d731d0432c34351ca004338937e8b4b38439316d1810471d19dba464de35ca979adf1bf391d27faa10ff39ea01f8b143670b9454e24ad11ac2c91fe956c93a5000000000000000000000000000000000000000000000000000000000000001b27f5f172c7794d49a3bd4b4d8d0614c4bded04cf3a39eb0cf3d88c883f2aa3ea0e7e91bfef9196d02da890204ff2fa71269b532ab2edb8c434d7cf571b60a7b6000000000000000000000000000000000000000000000000000000000000002d616c7468656131687630646372396c326c3039306a7874757875386e7361326a6d366833303778636d6634737700000000000000000000000000000000000000 \ No newline at end of file diff --git a/orchestrator/gbt/src/default-config.toml b/orchestrator/gbt/src/default-config.toml index 235b47b85..0137d0d87 100644 --- a/orchestrator/gbt/src/default-config.toml +++ b/orchestrator/gbt/src/default-config.toml @@ -13,10 +13,6 @@ batch_request_mode = "ProfitableOnly" [relayer.valset_relaying_mode] mode = "Altruistic" -# [relayer.valset_relaying_mode] -# mode = "ProfitableOnly" -# margin = 1.5 - [relayer.batch_relaying_mode] mode = "ProfitableOnly" margin = 1.1 diff --git a/orchestrator/gbt/src/relayer.rs b/orchestrator/gbt/src/relayer.rs index ca7462784..d32c09b5d 100644 --- a/orchestrator/gbt/src/relayer.rs +++ b/orchestrator/gbt/src/relayer.rs @@ -4,6 +4,7 @@ use gravity_utils::{ connection_prep::{ check_for_eth, check_for_fee, create_rpc_connections, wait_for_cosmos_node_ready, }, + deep_space::address::Address as CosmosAddress, error::GravityError, types::{BatchRequestMode, RelayerConfig}, }; @@ -71,6 +72,10 @@ pub async fn relayer( }; info!("Gravity contract address {}", contract_address); + // TODO add additional arg param to override it + // by default the address will be taken from the cosmos_key + let reward_recipient: CosmosAddress = cosmos_key.to_address(&contact.get_prefix()).unwrap(); + // setup and explain relayer settings if let Some(fee) = args.fees.clone() { if config.batch_request_mode != BatchRequestMode::None { @@ -94,6 +99,7 @@ pub async fn relayer( contract_address, params.gravity_id, config, + reward_recipient, ) .await } diff --git a/orchestrator/gbt/src/utils.rs b/orchestrator/gbt/src/utils.rs index 82209e9e9..c028f5198 100644 --- a/orchestrator/gbt/src/utils.rs +++ b/orchestrator/gbt/src/utils.rs @@ -8,14 +8,11 @@ pub const TIMEOUT: Duration = Duration::from_secs(60); pub fn print_relaying_explanation(input: &RelayerConfig, batch_requests: bool) { info!("Relaying from Cosmos => Ethereum is enabled, this will cost ETH"); match input.valset_relaying_mode { - ValsetRelayingMode::ProfitableOnly {margin} => info!( - "This relayer will only relay validator set updates if they have a profitable reward with at least {} margin", margin - ), ValsetRelayingMode::Altruistic => info!( "This relayer will relay validator set updates altruistically if required by the network" ), ValsetRelayingMode::EveryValset => warn!( - "This relayer will relay every validator set update. This will cost a lot of ETH!" + "This relayer will relay every validator set update. " ), } match (input.batch_request_mode, batch_requests) { diff --git a/orchestrator/gravity_proto/src/prost/cosmos.bank.v1beta1.rs b/orchestrator/gravity_proto/src/prost/cosmos.bank.v1beta1.rs index 4919a6eef..e4bd3f4ba 100644 --- a/orchestrator/gravity_proto/src/prost/cosmos.bank.v1beta1.rs +++ b/orchestrator/gravity_proto/src/prost/cosmos.bank.v1beta1.rs @@ -43,14 +43,14 @@ pub struct Supply { /// denomination unit of the basic token. #[derive(Clone, PartialEq, ::prost::Message)] pub struct DenomUnit { - /// denom represents the string name of the given denom unit (e.g anom). + /// denom represents the string name of the given denom unit (e.g uatom). #[prost(string, tag="1")] pub denom: ::prost::alloc::string::String, /// exponent represents power of 10 exponent that one must /// raise the base_denom to in order to equal the given DenomUnit's denom /// 1 denom = 1^exponent base_denom - /// (e.g. with a base_denom of anom, one can create a DenomUnit of 'nom' with - /// exponent = 18, thus: 1 nom = 10^18 anom). + /// (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with + /// exponent = 6, thus: 1 atom = 10^6 uatom). #[prost(uint32, tag="2")] pub exponent: u32, /// aliases is a list of string aliases for the given denom @@ -73,10 +73,10 @@ pub struct Metadata { /// displayed in clients. #[prost(string, tag="4")] pub display: ::prost::alloc::string::String, - /// name defines the name of the token (eg: Onomy Nom) + /// name defines the name of the token (eg: Cosmos Atom) #[prost(string, tag="5")] pub name: ::prost::alloc::string::String, - /// symbol is the token symbol usually shown on exchanges (eg: NOM). This can + /// symbol is the token symbol usually shown on exchanges (eg: ATOM). This can /// be the same as the display. #[prost(string, tag="6")] pub symbol: ::prost::alloc::string::String, diff --git a/orchestrator/gravity_proto/src/prost/gravity.v1.rs b/orchestrator/gravity_proto/src/prost/gravity.v1.rs index 85cacb82d..c7d9a70dc 100644 --- a/orchestrator/gravity_proto/src/prost/gravity.v1.rs +++ b/orchestrator/gravity_proto/src/prost/gravity.v1.rs @@ -29,7 +29,7 @@ pub struct Attestation { /// The contract address on ETH of the token, this could be a Cosmos /// originated token, if so it will be the ERC20 address of the representation /// (note: developers should look up the token symbol using the address on ETH to display for UI) -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct Erc20Token { #[prost(string, tag="1")] pub contract: ::prost::alloc::string::String, @@ -50,12 +50,12 @@ pub enum ClaimType { ValsetUpdated = 5, } /// IDSet represents a set of IDs -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct IdSet { #[prost(uint64, repeated, tag="1")] pub ids: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct BatchFees { #[prost(string, tag="1")] pub token: ::prost::alloc::string::String, @@ -65,7 +65,7 @@ pub struct BatchFees { pub tx_count: u64, } /// OutgoingTxBatch represents a batch of transactions going from gravity to ETH -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct OutgoingTxBatch { #[prost(uint64, tag="1")] pub batch_nonce: u64, @@ -79,7 +79,7 @@ pub struct OutgoingTxBatch { pub block: u64, } /// OutgoingTransferTx represents an individual send from gravity to ETH -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct OutgoingTransferTx { #[prost(uint64, tag="1")] pub id: u64, @@ -93,7 +93,7 @@ pub struct OutgoingTransferTx { pub erc20_fee: ::core::option::Option, } /// OutgoingLogicCall represents an individual logic call from gravity to ETH -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct OutgoingLogicCall { #[prost(message, repeated, tag="1")] pub transfers: ::prost::alloc::vec::Vec, @@ -112,16 +112,8 @@ pub struct OutgoingLogicCall { #[prost(uint64, tag="8")] pub block: u64, } -/// SignType defines messages that have been signed by an orchestrator -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum SignType { - Unspecified = 0, - OrchestratorSignedMultiSigUpdate = 1, - OrchestratorSignedWithdrawBatch = 2, -} /// BridgeValidator represents a validator's ETH address and its power -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct BridgeValidator { #[prost(uint64, tag="1")] pub power: u64, @@ -131,7 +123,7 @@ pub struct BridgeValidator { /// Valset is the Ethereum Bridge Multsig Set, each gravity validator also /// maintains an ETH key to sign messages, these are used to check signatures on /// ETH because of the significant gas savings -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct Valset { #[prost(uint64, tag="1")] pub nonce: u64, @@ -141,16 +133,16 @@ pub struct Valset { pub height: u64, #[prost(string, tag="4")] pub reward_amount: ::prost::alloc::string::String, - /// the reward token in it's Ethereum hex address representation + /// the cosmos native reward token/denom #[prost(string, tag="5")] - pub reward_token: ::prost::alloc::string::String, + pub reward_denom: ::prost::alloc::string::String, } /// LastObservedEthereumBlockHeight stores the last observed /// Ethereum block height along with the Cosmos block height that /// it was observed at. These two numbers can be used to project /// outward and always produce batches with timeouts in the future /// even if no Ethereum block height has been relayed for a long time -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct LastObservedEthereumBlockHeight { #[prost(uint64, tag="1")] pub cosmos_block_height: u64, @@ -159,7 +151,7 @@ pub struct LastObservedEthereumBlockHeight { } /// This records the relationship between an ERC20 token and the denom /// of the corresponding Cosmos originated asset -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct Erc20ToDenom { #[prost(string, tag="1")] pub erc20: ::prost::alloc::string::String, @@ -167,12 +159,12 @@ pub struct Erc20ToDenom { pub denom: ::prost::alloc::string::String, } /// UnhaltBridgeProposal defines a custom governance proposal useful for restoring -/// the bridge after a oracle disagreement. Once this proposal is passed bridge state will roll back events +/// the bridge after a oracle disagreement. Once this proposal is passed bridge state will roll back events /// to the nonce provided in target_nonce if and only if those events have not yet been observed (executed on the Cosmos chain). This allows for easy /// handling of cases where for example an Ethereum hardfork has occured and more than 1/3 of the vlaidtor set /// disagrees with the rest. Normally this would require a chain halt, manual genesis editing and restar to resolve /// with this feature a governance proposal can be used instead -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct UnhaltBridgeProposal { #[prost(string, tag="1")] pub title: ::prost::alloc::string::String, @@ -185,7 +177,7 @@ pub struct UnhaltBridgeProposal { /// fashion. A list of destination addresses and an amount per airdrop recipient is provided. The funds for this /// airdrop are removed from the Community Pool, if the community pool does not have sufficient funding to perform /// the airdrop to all provided recipients nothing will occur -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct AirdropProposal { #[prost(string, tag="1")] pub title: ::prost::alloc::string::String, @@ -231,7 +223,7 @@ pub struct IbcMetadataProposal { /// ETH_ADDRESS /// This is a hex encoded 0x Ethereum public key that will be used by this validator /// on Ethereum -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSetOrchestratorAddress { #[prost(string, tag="1")] pub validator: ::prost::alloc::string::String, @@ -240,7 +232,7 @@ pub struct MsgSetOrchestratorAddress { #[prost(string, tag="3")] pub eth_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSetOrchestratorAddressResponse { } /// MsgValsetConfirm @@ -258,7 +250,7 @@ pub struct MsgSetOrchestratorAddressResponse { /// signatures it is then possible for anyone to view these signatures in the /// chain store and submit them to Ethereum to update the validator set /// ------------- -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgValsetConfirm { #[prost(uint64, tag="1")] pub nonce: u64, @@ -269,7 +261,7 @@ pub struct MsgValsetConfirm { #[prost(string, tag="4")] pub signature: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgValsetConfirmResponse { } /// MsgSendToEth @@ -295,7 +287,7 @@ pub struct MsgSendToEth { #[prost(message, optional, tag="4")] pub bridge_fee: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSendToEthResponse { } /// MsgRequestBatch @@ -307,14 +299,14 @@ pub struct MsgSendToEthResponse { /// batch, sign it, submit the signatures with a MsgConfirmBatch before a relayer /// can finally submit the batch /// ------------- -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgRequestBatch { #[prost(string, tag="1")] pub sender: ::prost::alloc::string::String, #[prost(string, tag="2")] pub denom: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgRequestBatchResponse { } /// MsgConfirmBatch @@ -325,7 +317,7 @@ pub struct MsgRequestBatchResponse { /// (TODO determine this without nondeterminism) This message includes the batch /// as well as an Ethereum signature over this batch by the validator /// ------------- -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgConfirmBatch { #[prost(uint64, tag="1")] pub nonce: u64, @@ -338,7 +330,7 @@ pub struct MsgConfirmBatch { #[prost(string, tag="5")] pub signature: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgConfirmBatchResponse { } /// MsgConfirmLogicCall @@ -349,7 +341,7 @@ pub struct MsgConfirmBatchResponse { /// (TODO determine this without nondeterminism) This message includes the batch /// as well as an Ethereum signature over this batch by the validator /// ------------- -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgConfirmLogicCall { #[prost(string, tag="1")] pub invalidation_id: ::prost::alloc::string::String, @@ -362,7 +354,7 @@ pub struct MsgConfirmLogicCall { #[prost(string, tag="5")] pub signature: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgConfirmLogicCallResponse { } /// MsgSendToCosmosClaim @@ -370,7 +362,7 @@ pub struct MsgConfirmLogicCallResponse { /// claimed to have seen the deposit enter the ethereum blockchain coins are /// issued to the Cosmos address in question /// ------------- -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSendToCosmosClaim { #[prost(uint64, tag="1")] pub event_nonce: u64, @@ -387,12 +379,12 @@ pub struct MsgSendToCosmosClaim { #[prost(string, tag="7")] pub orchestrator: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSendToCosmosClaimResponse { } /// BatchSendToEthClaim claims that a batch of send to eth /// operations on the bridge contract was executed. -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgBatchSendToEthClaim { #[prost(uint64, tag="1")] pub event_nonce: u64, @@ -405,13 +397,13 @@ pub struct MsgBatchSendToEthClaim { #[prost(string, tag="5")] pub orchestrator: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgBatchSendToEthClaimResponse { } /// ERC20DeployedClaim allows the Cosmos module /// to learn about an ERC20 that someone deployed /// to represent a Cosmos asset -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgErc20DeployedClaim { #[prost(uint64, tag="1")] pub event_nonce: u64, @@ -430,12 +422,12 @@ pub struct MsgErc20DeployedClaim { #[prost(string, tag="8")] pub orchestrator: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgErc20DeployedClaimResponse { } /// This informs the Cosmos module that a logic /// call has been executed -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgLogicCallExecutedClaim { #[prost(uint64, tag="1")] pub event_nonce: u64, @@ -448,12 +440,12 @@ pub struct MsgLogicCallExecutedClaim { #[prost(string, tag="5")] pub orchestrator: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgLogicCallExecutedClaimResponse { } /// This informs the Cosmos module that a validator /// set has been updated. -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgValsetUpdatedClaim { #[prost(uint64, tag="1")] pub event_nonce: u64, @@ -466,29 +458,31 @@ pub struct MsgValsetUpdatedClaim { #[prost(string, tag="5")] pub reward_amount: ::prost::alloc::string::String, #[prost(string, tag="6")] - pub reward_token: ::prost::alloc::string::String, + pub reward_denom: ::prost::alloc::string::String, #[prost(string, tag="7")] + pub reward_recipient: ::prost::alloc::string::String, + #[prost(string, tag="8")] pub orchestrator: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgValsetUpdatedClaimResponse { } /// This call allows the sender (and only the sender) /// to cancel a given MsgSendToEth and recieve a refund /// of the tokens -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgCancelSendToEth { #[prost(uint64, tag="1")] pub transaction_id: u64, #[prost(string, tag="2")] pub sender: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgCancelSendToEthResponse { } /// This call allows anyone to submit evidence that a /// validator has signed a valset, batch, or logic call that never -/// existed on the Cosmos chain. +/// existed on the Cosmos chain. /// Subject contains the batch, valset, or logic call. #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgSubmitBadSignatureEvidence { @@ -499,7 +493,7 @@ pub struct MsgSubmitBadSignatureEvidence { #[prost(string, tag="3")] pub sender: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct MsgSubmitBadSignatureEvidenceResponse { } # [doc = r" Generated client implementations."] pub mod msg_client { # ! [allow (unused_variables , dead_code , missing_docs , clippy :: let_unit_value ,)] use tonic :: codegen :: * ; # [doc = " Msg defines the state transitions possible within gravity"] # [derive (Debug , Clone)] pub struct MsgClient < T > { inner : tonic :: client :: Grpc < T > , } impl MsgClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > MsgClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as Body > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor < F > (inner : T , interceptor : F) -> MsgClient < InterceptedService < T , F >> where F : tonic :: service :: Interceptor , T : tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody > , Response = http :: Response << T as tonic :: client :: GrpcService < tonic :: body :: BoxBody >> :: ResponseBody > > , < T as tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody >> > :: Error : Into < StdError > + Send + Sync , { MsgClient :: new (InterceptedService :: new (inner , interceptor)) } # [doc = r" Compress requests with `gzip`."] # [doc = r""] # [doc = r" This requires the server to support it otherwise it might respond with an"] # [doc = r" error."] pub fn send_gzip (mut self) -> Self { self . inner = self . inner . send_gzip () ; self } # [doc = r" Enable decompressing responses with `gzip`."] pub fn accept_gzip (mut self) -> Self { self . inner = self . inner . accept_gzip () ; self } pub async fn valset_confirm (& mut self , request : impl tonic :: IntoRequest < super :: MsgValsetConfirm > ,) -> Result < tonic :: Response < super :: MsgValsetConfirmResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/ValsetConfirm") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn send_to_eth (& mut self , request : impl tonic :: IntoRequest < super :: MsgSendToEth > ,) -> Result < tonic :: Response < super :: MsgSendToEthResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/SendToEth") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn request_batch (& mut self , request : impl tonic :: IntoRequest < super :: MsgRequestBatch > ,) -> Result < tonic :: Response < super :: MsgRequestBatchResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/RequestBatch") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn confirm_batch (& mut self , request : impl tonic :: IntoRequest < super :: MsgConfirmBatch > ,) -> Result < tonic :: Response < super :: MsgConfirmBatchResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/ConfirmBatch") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn confirm_logic_call (& mut self , request : impl tonic :: IntoRequest < super :: MsgConfirmLogicCall > ,) -> Result < tonic :: Response < super :: MsgConfirmLogicCallResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/ConfirmLogicCall") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn send_to_cosmos_claim (& mut self , request : impl tonic :: IntoRequest < super :: MsgSendToCosmosClaim > ,) -> Result < tonic :: Response < super :: MsgSendToCosmosClaimResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/SendToCosmosClaim") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_send_to_eth_claim (& mut self , request : impl tonic :: IntoRequest < super :: MsgBatchSendToEthClaim > ,) -> Result < tonic :: Response < super :: MsgBatchSendToEthClaimResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/BatchSendToEthClaim") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_update_claim (& mut self , request : impl tonic :: IntoRequest < super :: MsgValsetUpdatedClaim > ,) -> Result < tonic :: Response < super :: MsgValsetUpdatedClaimResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/ValsetUpdateClaim") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn erc20_deployed_claim (& mut self , request : impl tonic :: IntoRequest < super :: MsgErc20DeployedClaim > ,) -> Result < tonic :: Response < super :: MsgErc20DeployedClaimResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/ERC20DeployedClaim") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn logic_call_executed_claim (& mut self , request : impl tonic :: IntoRequest < super :: MsgLogicCallExecutedClaim > ,) -> Result < tonic :: Response < super :: MsgLogicCallExecutedClaimResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/LogicCallExecutedClaim") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn set_orchestrator_address (& mut self , request : impl tonic :: IntoRequest < super :: MsgSetOrchestratorAddress > ,) -> Result < tonic :: Response < super :: MsgSetOrchestratorAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/SetOrchestratorAddress") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn cancel_send_to_eth (& mut self , request : impl tonic :: IntoRequest < super :: MsgCancelSendToEth > ,) -> Result < tonic :: Response < super :: MsgCancelSendToEthResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/CancelSendToEth") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn submit_bad_signature_evidence (& mut self , request : impl tonic :: IntoRequest < super :: MsgSubmitBadSignatureEvidence > ,) -> Result < tonic :: Response < super :: MsgSubmitBadSignatureEvidenceResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Msg/SubmitBadSignatureEvidence") ; self . inner . unary (request . into_request () , path , codec) . await } } }// Params represent the Gravity genesis and store parameters @@ -631,6 +625,9 @@ pub struct Params { /// from Ethereum to the bridge #[prost(string, repeated, tag="19")] pub ethereum_blacklist: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// the pair of eth token and denom to automatically swap once the erc20 token is bridged. + #[prost(message, optional, tag="50")] + pub erc20_to_denom_permanent_swap: ::core::option::Option, } /// GenesisState struct, containing all persistant data required by the Gravity module #[derive(Clone, PartialEq, ::prost::Message)] @@ -661,7 +658,7 @@ pub struct GenesisState { pub unbatched_transfers: ::prost::alloc::vec::Vec, } /// GravityCounters contains the many noces and counters required to maintain the bridge state in the genesis -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct GravityNonces { /// the nonce of the last generated validator set #[prost(uint64, tag="1")] @@ -689,7 +686,7 @@ pub struct GravityNonces { #[prost(uint64, tag="7")] pub last_batch_id: u64, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryParamsRequest { } #[derive(Clone, PartialEq, ::prost::Message)] @@ -697,179 +694,179 @@ pub struct QueryParamsResponse { #[prost(message, optional, tag="1")] pub params: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryCurrentValsetRequest { } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryCurrentValsetResponse { #[prost(message, optional, tag="1")] pub valset: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetRequestRequest { #[prost(uint64, tag="1")] pub nonce: u64, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetRequestResponse { #[prost(message, optional, tag="1")] pub valset: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetConfirmRequest { #[prost(uint64, tag="1")] pub nonce: u64, #[prost(string, tag="2")] pub address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetConfirmResponse { #[prost(message, optional, tag="1")] pub confirm: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetConfirmsByNonceRequest { #[prost(uint64, tag="1")] pub nonce: u64, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryValsetConfirmsByNonceResponse { #[prost(message, repeated, tag="1")] pub confirms: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastValsetRequestsRequest { } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastValsetRequestsResponse { #[prost(message, repeated, tag="1")] pub valsets: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingValsetRequestByAddrRequest { #[prost(string, tag="1")] pub address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingValsetRequestByAddrResponse { #[prost(message, repeated, tag="1")] pub valsets: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchFeeRequest { } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchFeeResponse { #[prost(message, repeated, tag="1")] pub batch_fees: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingBatchRequestByAddrRequest { #[prost(string, tag="1")] pub address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingBatchRequestByAddrResponse { #[prost(message, repeated, tag="1")] pub batch: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingLogicCallByAddrRequest { #[prost(string, tag="1")] pub address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastPendingLogicCallByAddrResponse { #[prost(message, repeated, tag="1")] pub call: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryOutgoingTxBatchesRequest { } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryOutgoingTxBatchesResponse { #[prost(message, repeated, tag="1")] pub batches: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryOutgoingLogicCallsRequest { } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryOutgoingLogicCallsResponse { #[prost(message, repeated, tag="1")] pub calls: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchRequestByNonceRequest { #[prost(uint64, tag="1")] pub nonce: u64, #[prost(string, tag="2")] pub contract_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchRequestByNonceResponse { #[prost(message, optional, tag="1")] pub batch: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchConfirmsRequest { #[prost(uint64, tag="1")] pub nonce: u64, #[prost(string, tag="2")] pub contract_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryBatchConfirmsResponse { #[prost(message, repeated, tag="1")] pub confirms: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLogicConfirmsRequest { #[prost(bytes="vec", tag="1")] pub invalidation_id: ::prost::alloc::vec::Vec, #[prost(uint64, tag="2")] pub invalidation_nonce: u64, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLogicConfirmsResponse { #[prost(message, repeated, tag="1")] pub confirms: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastEventNonceByAddrRequest { #[prost(string, tag="1")] pub address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryLastEventNonceByAddrResponse { #[prost(uint64, tag="1")] pub event_nonce: u64, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryErc20ToDenomRequest { #[prost(string, tag="1")] pub erc20: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryErc20ToDenomResponse { #[prost(string, tag="1")] pub denom: ::prost::alloc::string::String, #[prost(bool, tag="2")] pub cosmos_originated: bool, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDenomToErc20Request { #[prost(string, tag="1")] pub denom: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDenomToErc20Response { #[prost(string, tag="1")] pub erc20: ::prost::alloc::string::String, #[prost(bool, tag="2")] pub cosmos_originated: bool, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryAttestationsRequest { #[prost(uint64, tag="1")] pub limit: u64, @@ -879,52 +876,59 @@ pub struct QueryAttestationsResponse { #[prost(message, repeated, tag="1")] pub attestations: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByValidatorAddress { #[prost(string, tag="1")] pub validator_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByValidatorAddressResponse { #[prost(string, tag="1")] pub eth_address: ::prost::alloc::string::String, #[prost(string, tag="2")] pub orchestrator_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByEthAddress { #[prost(string, tag="1")] pub eth_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByEthAddressResponse { #[prost(string, tag="1")] pub validator_address: ::prost::alloc::string::String, #[prost(string, tag="2")] pub orchestrator_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByOrchestratorAddress { #[prost(string, tag="1")] pub orchestrator_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryDelegateKeysByOrchestratorAddressResponse { #[prost(string, tag="1")] pub validator_address: ::prost::alloc::string::String, #[prost(string, tag="2")] pub eth_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryPendingSendToEth { #[prost(string, tag="1")] pub sender_address: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, ::prost::Message)] pub struct QueryPendingSendToEthResponse { #[prost(message, repeated, tag="1")] pub transfers_in_batches: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="2")] pub unbatched_transfers: ::prost::alloc::vec::Vec, } -# [doc = r" Generated client implementations."] pub mod query_client { # ! [allow (unused_variables , dead_code , missing_docs , clippy :: let_unit_value ,)] use tonic :: codegen :: * ; # [doc = " Query defines the gRPC querier service"] # [derive (Debug , Clone)] pub struct QueryClient < T > { inner : tonic :: client :: Grpc < T > , } impl QueryClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > QueryClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as Body > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor < F > (inner : T , interceptor : F) -> QueryClient < InterceptedService < T , F >> where F : tonic :: service :: Interceptor , T : tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody > , Response = http :: Response << T as tonic :: client :: GrpcService < tonic :: body :: BoxBody >> :: ResponseBody > > , < T as tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody >> > :: Error : Into < StdError > + Send + Sync , { QueryClient :: new (InterceptedService :: new (inner , interceptor)) } # [doc = r" Compress requests with `gzip`."] # [doc = r""] # [doc = r" This requires the server to support it otherwise it might respond with an"] # [doc = r" error."] pub fn send_gzip (mut self) -> Self { self . inner = self . inner . send_gzip () ; self } # [doc = r" Enable decompressing responses with `gzip`."] pub fn accept_gzip (mut self) -> Self { self . inner = self . inner . accept_gzip () ; self } # [doc = " Deployments queries deployments"] pub async fn params (& mut self , request : impl tonic :: IntoRequest < super :: QueryParamsRequest > ,) -> Result < tonic :: Response < super :: QueryParamsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/Params") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn current_valset (& mut self , request : impl tonic :: IntoRequest < super :: QueryCurrentValsetRequest > ,) -> Result < tonic :: Response < super :: QueryCurrentValsetResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/CurrentValset") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_request (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetRequestRequest > ,) -> Result < tonic :: Response < super :: QueryValsetRequestResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetRequest") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_confirm (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetConfirmRequest > ,) -> Result < tonic :: Response < super :: QueryValsetConfirmResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetConfirm") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_confirms_by_nonce (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetConfirmsByNonceRequest > ,) -> Result < tonic :: Response < super :: QueryValsetConfirmsByNonceResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetConfirmsByNonce") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_valset_requests (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastValsetRequestsRequest > ,) -> Result < tonic :: Response < super :: QueryLastValsetRequestsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastValsetRequests") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_valset_request_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingValsetRequestByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingValsetRequestByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingValsetRequestByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_batch_request_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingBatchRequestByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingBatchRequestByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingBatchRequestByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_logic_call_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingLogicCallByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingLogicCallByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingLogicCallByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_event_nonce_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastEventNonceByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastEventNonceByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastEventNonceByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_fees (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchFeeRequest > ,) -> Result < tonic :: Response < super :: QueryBatchFeeResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchFees") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn outgoing_tx_batches (& mut self , request : impl tonic :: IntoRequest < super :: QueryOutgoingTxBatchesRequest > ,) -> Result < tonic :: Response < super :: QueryOutgoingTxBatchesResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/OutgoingTxBatches") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn outgoing_logic_calls (& mut self , request : impl tonic :: IntoRequest < super :: QueryOutgoingLogicCallsRequest > ,) -> Result < tonic :: Response < super :: QueryOutgoingLogicCallsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/OutgoingLogicCalls") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_request_by_nonce (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchRequestByNonceRequest > ,) -> Result < tonic :: Response < super :: QueryBatchRequestByNonceResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchRequestByNonce") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_confirms (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchConfirmsRequest > ,) -> Result < tonic :: Response < super :: QueryBatchConfirmsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchConfirms") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn logic_confirms (& mut self , request : impl tonic :: IntoRequest < super :: QueryLogicConfirmsRequest > ,) -> Result < tonic :: Response < super :: QueryLogicConfirmsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LogicConfirms") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn erc20_to_denom (& mut self , request : impl tonic :: IntoRequest < super :: QueryErc20ToDenomRequest > ,) -> Result < tonic :: Response < super :: QueryErc20ToDenomResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ERC20ToDenom") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn denom_to_erc20 (& mut self , request : impl tonic :: IntoRequest < super :: QueryDenomToErc20Request > ,) -> Result < tonic :: Response < super :: QueryDenomToErc20Response > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/DenomToERC20") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_attestations (& mut self , request : impl tonic :: IntoRequest < super :: QueryAttestationsRequest > ,) -> Result < tonic :: Response < super :: QueryAttestationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetAttestations") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_validator (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByValidatorAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByValidatorAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByValidator") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_eth (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByEthAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByEthAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByEth") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_orchestrator (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByOrchestratorAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByOrchestratorAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByOrchestrator") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_pending_send_to_eth (& mut self , request : impl tonic :: IntoRequest < super :: QueryPendingSendToEth > ,) -> Result < tonic :: Response < super :: QueryPendingSendToEthResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetPendingSendToEth") ; self . inner . unary (request . into_request () , path , codec) . await } } } \ No newline at end of file +# [doc = r" Generated client implementations."] pub mod query_client { # ! [allow (unused_variables , dead_code , missing_docs , clippy :: let_unit_value ,)] use tonic :: codegen :: * ; # [doc = " Query defines the gRPC querier service"] # [derive (Debug , Clone)] pub struct QueryClient < T > { inner : tonic :: client :: Grpc < T > , } impl QueryClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > QueryClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as Body > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor < F > (inner : T , interceptor : F) -> QueryClient < InterceptedService < T , F >> where F : tonic :: service :: Interceptor , T : tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody > , Response = http :: Response << T as tonic :: client :: GrpcService < tonic :: body :: BoxBody >> :: ResponseBody > > , < T as tonic :: codegen :: Service < http :: Request < tonic :: body :: BoxBody >> > :: Error : Into < StdError > + Send + Sync , { QueryClient :: new (InterceptedService :: new (inner , interceptor)) } # [doc = r" Compress requests with `gzip`."] # [doc = r""] # [doc = r" This requires the server to support it otherwise it might respond with an"] # [doc = r" error."] pub fn send_gzip (mut self) -> Self { self . inner = self . inner . send_gzip () ; self } # [doc = r" Enable decompressing responses with `gzip`."] pub fn accept_gzip (mut self) -> Self { self . inner = self . inner . accept_gzip () ; self } # [doc = " Deployments queries deployments"] pub async fn params (& mut self , request : impl tonic :: IntoRequest < super :: QueryParamsRequest > ,) -> Result < tonic :: Response < super :: QueryParamsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/Params") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn current_valset (& mut self , request : impl tonic :: IntoRequest < super :: QueryCurrentValsetRequest > ,) -> Result < tonic :: Response < super :: QueryCurrentValsetResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/CurrentValset") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_request (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetRequestRequest > ,) -> Result < tonic :: Response < super :: QueryValsetRequestResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetRequest") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_confirm (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetConfirmRequest > ,) -> Result < tonic :: Response < super :: QueryValsetConfirmResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetConfirm") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn valset_confirms_by_nonce (& mut self , request : impl tonic :: IntoRequest < super :: QueryValsetConfirmsByNonceRequest > ,) -> Result < tonic :: Response < super :: QueryValsetConfirmsByNonceResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ValsetConfirmsByNonce") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_valset_requests (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastValsetRequestsRequest > ,) -> Result < tonic :: Response < super :: QueryLastValsetRequestsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastValsetRequests") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_valset_request_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingValsetRequestByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingValsetRequestByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingValsetRequestByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_batch_request_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingBatchRequestByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingBatchRequestByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingBatchRequestByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_pending_logic_call_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastPendingLogicCallByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastPendingLogicCallByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastPendingLogicCallByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn last_event_nonce_by_addr (& mut self , request : impl tonic :: IntoRequest < super :: QueryLastEventNonceByAddrRequest > ,) -> Result < tonic :: Response < super :: QueryLastEventNonceByAddrResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LastEventNonceByAddr") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_fees (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchFeeRequest > ,) -> Result < tonic :: Response < super :: QueryBatchFeeResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchFees") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn outgoing_tx_batches (& mut self , request : impl tonic :: IntoRequest < super :: QueryOutgoingTxBatchesRequest > ,) -> Result < tonic :: Response < super :: QueryOutgoingTxBatchesResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/OutgoingTxBatches") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn outgoing_logic_calls (& mut self , request : impl tonic :: IntoRequest < super :: QueryOutgoingLogicCallsRequest > ,) -> Result < tonic :: Response < super :: QueryOutgoingLogicCallsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/OutgoingLogicCalls") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_request_by_nonce (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchRequestByNonceRequest > ,) -> Result < tonic :: Response < super :: QueryBatchRequestByNonceResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchRequestByNonce") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn batch_confirms (& mut self , request : impl tonic :: IntoRequest < super :: QueryBatchConfirmsRequest > ,) -> Result < tonic :: Response < super :: QueryBatchConfirmsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/BatchConfirms") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn logic_confirms (& mut self , request : impl tonic :: IntoRequest < super :: QueryLogicConfirmsRequest > ,) -> Result < tonic :: Response < super :: QueryLogicConfirmsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/LogicConfirms") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn erc20_to_denom (& mut self , request : impl tonic :: IntoRequest < super :: QueryErc20ToDenomRequest > ,) -> Result < tonic :: Response < super :: QueryErc20ToDenomResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/ERC20ToDenom") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn denom_to_erc20 (& mut self , request : impl tonic :: IntoRequest < super :: QueryDenomToErc20Request > ,) -> Result < tonic :: Response < super :: QueryDenomToErc20Response > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/DenomToERC20") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_attestations (& mut self , request : impl tonic :: IntoRequest < super :: QueryAttestationsRequest > ,) -> Result < tonic :: Response < super :: QueryAttestationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetAttestations") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_validator (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByValidatorAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByValidatorAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByValidator") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_eth (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByEthAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByEthAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByEth") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_delegate_key_by_orchestrator (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegateKeysByOrchestratorAddress > ,) -> Result < tonic :: Response < super :: QueryDelegateKeysByOrchestratorAddressResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetDelegateKeyByOrchestrator") ; self . inner . unary (request . into_request () , path , codec) . await } pub async fn get_pending_send_to_eth (& mut self , request : impl tonic :: IntoRequest < super :: QueryPendingSendToEth > ,) -> Result < tonic :: Response < super :: QueryPendingSendToEthResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/gravity.v1.Query/GetPendingSendToEth") ; self . inner . unary (request . into_request () , path , codec) . await } } }/// SignType defines messages that have been signed by an orchestrator +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SignType { + Unspecified = 0, + OrchestratorSignedMultiSigUpdate = 1, + OrchestratorSignedWithdrawBatch = 2, +} diff --git a/orchestrator/gravity_utils/Cargo.toml b/orchestrator/gravity_utils/Cargo.toml index 1671a3661..b6d852ecf 100644 --- a/orchestrator/gravity_utils/Cargo.toml +++ b/orchestrator/gravity_utils/Cargo.toml @@ -5,7 +5,7 @@ name = "gravity_utils" version = "0.1.0" [dependencies] -clarity = { git = "https://github.com/onomyprotocol/clarity.git", rev = "52bc13f7d2778d293e2d787528d481604620a281" } +clarity = { git = "https://github.com/onomyprotocol/clarity.git", rev = "0b69d4aabae546a2d1bd296f04ba64a8a1546b9a" } deep_space = { git = "https://github.com/onomyprotocol/deep_space.git", rev = "20e9f0ce59412a305af4a3d6e9bf6c22413a2f81" } gravity_proto = { path = "../gravity_proto/" } log = "0.4" @@ -16,7 +16,7 @@ tokio = { version = "1.17", features = ["macros", "rt-multi-thread"] } tonic = "0.6" u64_array_bigints = { version = "0.3", default-features = false, features = ["serde_support"] } url = "2" -web30 = { git = "https://github.com/onomyprotocol/web30.git", rev = "645c0509246fedff01aed1aa4c26eb804f3fec1a" } +web30 = { git = "https://github.com/onomyprotocol/web30.git", rev = "3e009bf55c7740ef5bb692c1f1ec8d9724687dbc" } [dev_dependencies] rand = "0.8" diff --git a/orchestrator/gravity_utils/src/prices.rs b/orchestrator/gravity_utils/src/prices.rs index 63f93d43d..2a5f808eb 100644 --- a/orchestrator/gravity_utils/src/prices.rs +++ b/orchestrator/gravity_utils/src/prices.rs @@ -18,19 +18,16 @@ pub async fn get_weth_price( return Ok(u256!(0)); } - // TODO: Make sure the market is not too thin - let price = web3 - .get_uniswap_price( - pubkey, - token, - *WETH_CONTRACT_ADDRESS, - None, - amount, - None, - None, - ) - .await; - price + web3.get_uniswap_price( + pubkey, + token, + *WETH_CONTRACT_ADDRESS, + None, + amount, + None, + None, + ) + .await } /// utility function, gets the price of a given ER20 token in uniswap in DAI given the erc20 address and amount @@ -46,17 +43,14 @@ pub async fn get_dai_price( return Ok(u256!(0)); } - // TODO: Make sure the market is not too thin - let price = web3 - .get_uniswap_price( - pubkey, - token, - *DAI_CONTRACT_ADDRESS, - None, - amount, - None, - None, - ) - .await; - price + web3.get_uniswap_price( + pubkey, + token, + *DAI_CONTRACT_ADDRESS, + None, + amount, + None, + None, + ) + .await } diff --git a/orchestrator/gravity_utils/src/types/config.rs b/orchestrator/gravity_utils/src/types/config.rs index ac903b895..8d0364cbc 100644 --- a/orchestrator/gravity_utils/src/types/config.rs +++ b/orchestrator/gravity_utils/src/types/config.rs @@ -72,13 +72,8 @@ impl From for RelayerConfig { } /// The various possible modes for relaying validator set updates -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] pub enum ValsetRelayingMode { - /// Only ever relay profitable valsets, regardless of all other - /// considerations. Profitable being defined as the value of - /// the reward token in uniswap being greater than WETH cost of - /// relaying * margin - ProfitableOnly { margin: f64 }, /// Relay validator sets when continued operation of the chain /// requires it, this will cost some ETH Altruistic, @@ -87,22 +82,16 @@ pub enum ValsetRelayingMode { } /// A version of valset relaying mode that's easy to serialize as toml -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct TomlValsetRelayingMode { mode: String, - margin: Option, } impl From for ValsetRelayingMode { fn from(input: TomlValsetRelayingMode) -> Self { - match input.mode.as_str() { - "ProfitableOnly" | "profitableonly" | "PROFITABLEONLY" => { - ValsetRelayingMode::ProfitableOnly { - margin: input.margin.unwrap(), - } - } - "Altruistic" | "altruistic" | "ALTRUISTIC" => ValsetRelayingMode::Altruistic, - "EveryValset" | "everyvalset" | "EVERYVALSET" => ValsetRelayingMode::EveryValset, + match input.mode.to_uppercase().as_str() { + "EVERYVALSET" => ValsetRelayingMode::EveryValset, + "ALTRUISTIC" => ValsetRelayingMode::Altruistic, _ => panic!("Invalid TomlValsetRelayingMode"), } } @@ -122,7 +111,7 @@ pub enum BatchRequestMode { /// A whitelisted token that will be relayed given the batch /// provides at least amount of this specific token -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] pub struct WhitelistToken { /// the amount which the batch must have to be relayed pub amount: Uint256, @@ -161,19 +150,15 @@ pub struct TomlBatchRelayingMode { impl From for BatchRelayingMode { fn from(input: TomlBatchRelayingMode) -> Self { - match input.mode.as_str() { - "EveryBatch" | "everybatch" | "EVERYBATCH" => BatchRelayingMode::EveryBatch, - "ProfitableOnly" | "profitableonly" | "PROFITABLEONLY" => { - BatchRelayingMode::ProfitableOnly { - margin: input.margin.unwrap(), - } - } - "ProfitableWithWhitelist" | "profitablewithwhitelist" | "PROFITABLEWITHWHITELIST" => { - BatchRelayingMode::ProfitableWithWhitelist { - margin: input.margin.unwrap(), - whitelist: input.whitelist.unwrap(), - } - } + match input.mode.to_uppercase().as_str() { + "EVERYBATCH" => BatchRelayingMode::EveryBatch, + "PROFITABLEONLY" => BatchRelayingMode::ProfitableOnly { + margin: input.margin.unwrap(), + }, + "PROFITABLEWITHWHITELIST" => BatchRelayingMode::ProfitableWithWhitelist { + margin: input.margin.unwrap(), + whitelist: input.whitelist.unwrap(), + }, _ => panic!("Bad TomlBatchRelayingMode"), } } @@ -194,7 +179,6 @@ fn default_logic_call_market_enabled() -> bool { fn default_valset_relaying_mode() -> TomlValsetRelayingMode { TomlValsetRelayingMode { mode: "Altruistic".to_string(), - margin: None, } } diff --git a/orchestrator/gravity_utils/src/types/ethereum_events.rs b/orchestrator/gravity_utils/src/types/ethereum_events.rs index 9c9454923..3df0bb243 100644 --- a/orchestrator/gravity_utils/src/types/ethereum_events.rs +++ b/orchestrator/gravity_utils/src/types/ethereum_events.rs @@ -9,7 +9,7 @@ use std::unimplemented; -use clarity::{constants::ZERO_ADDRESS, Address as EthAddress, Uint256}; +use clarity::{Address as EthAddress, Uint256}; use deep_space::{utils::bytes_to_hex_str, Address as CosmosAddress}; use serde::{Deserialize, Serialize}; use web30::types::Log; @@ -22,7 +22,6 @@ use crate::error::GravityError; const ONE_MEGABYTE: usize = 1000usize.pow(3); const U32_MAX: Uint256 = Uint256::from_u32(u32::MAX); const U64_MAX: Uint256 = Uint256::from_u64(u64::MAX); -const USIZE_MAX: Uint256 = Uint256::from_u128(usize::MAX as u128); /// A parsed struct representing the Ethereum event fired by the Gravity contract /// when the validator set is updated. @@ -32,7 +31,8 @@ pub struct ValsetUpdatedEvent { pub event_nonce: u64, pub block_height: Uint256, pub reward_amount: Uint256, - pub reward_token: Option, + pub reward_denom: String, + pub reward_recipient: String, pub members: Vec, } @@ -41,20 +41,20 @@ pub struct ValsetUpdatedEvent { struct ValsetDataBytes { pub event_nonce: u64, pub reward_amount: Uint256, - pub reward_token: Option, + pub reward_denom: String, + pub reward_recipient: String, pub members: Vec, } +// TODO refactor the entire file to parse by arg index instead of code duplication. impl ValsetUpdatedEvent { /// Decodes the data bytes of a valset log event, separated for easy testing fn decode_data_bytes(input: &[u8]) -> Result { - if input.len() < 6 * 32 { + if input.len() < 7 * 32 { return Err(GravityError::ValidationError( "too short for ValsetUpdatedEventData".to_string(), )); } - // first index is the event nonce, then the reward token, amount, following two have event data we don't - // care about, sixth index contains the length of the eth address array // event nonce let index_start = 0; @@ -74,112 +74,51 @@ impl ValsetUpdatedEvent { let reward_amount_data = &input[index_start..index_end]; let reward_amount = Uint256::from_bytes_be(reward_amount_data).unwrap(); - // reward token - let index_start = 2 * 32; - let index_end = index_start + 32; - let reward_token_data = &input[index_start..index_end]; - // addresses are 12 bytes shorter than the 32 byte field they are stored in - let reward_token = EthAddress::from_slice(&reward_token_data[12..]); - if let Err(e) = reward_token { - return Err(GravityError::ValidationError(format!( - "Bad reward address, must be incorrect parsing {:?}", - e - ))); - } - let reward_token = reward_token.unwrap(); - // zero address represents no reward, so we replace it here with a none - // for ease of checking in the future - let reward_token = if reward_token == ZERO_ADDRESS { - None - } else { - Some(reward_token) + let reward_denom = match parse_string(input, 2) { + Ok(v) => v, + Err(err) => return Err(err), }; - // below this we are parsing the dynamic data from the 6th index on - let index_start = 5 * 32; - let index_end = index_start + 32; - let eth_addresses_offset = index_end; - if input.len() < eth_addresses_offset { - return Err(GravityError::ValidationError( - "too short for dynamic data".to_string(), - )); - } + let reward_recipient = match parse_string(input, 3) { + Ok(v) => v, + Err(err) => return Err(err), + }; - let len_eth_addresses = Uint256::from_bytes_be(&input[index_start..index_end]).unwrap(); - if len_eth_addresses > USIZE_MAX { - return Err(GravityError::ValidationError( - "Ethereum array len overflow, probably incorrect parsing".to_string(), - )); - } - let len_eth_addresses: usize = len_eth_addresses.to_string().parse().unwrap(); - let index_start = (6 + len_eth_addresses) * 32; - let index_end = index_start + 32; - let powers_offset = index_end; - if input.len() < powers_offset { - return Err(GravityError::ValidationError( - "too short for dynamic data".to_string(), - )); - } + let validators_addresses: Vec = match parse_address_array(input, 4) { + Ok(v) => v, + Err(err) => return Err(err), + }; - let len_powers = Uint256::from_bytes_be(&input[index_start..index_end]).unwrap(); - if len_powers > USIZE_MAX { - return Err(GravityError::ValidationError( - "Powers array len overflow, probably incorrect parsing".to_string(), - )); - } - let len_powers: usize = len_eth_addresses.to_string().parse().unwrap(); - if len_powers != len_eth_addresses { + let validators_powers: Vec = match parse_uint256_array(input, 5) { + Ok(v) => v, + Err(err) => return Err(err), + }; + + if validators_addresses.len() != validators_powers.len() { return Err(GravityError::ValidationError( - "Array len mismatch, probably incorrect parsing".to_string(), + "validators_addresses len != validators_powers len".to_string(), )); } let mut validators = Vec::new(); - for i in 0..len_eth_addresses { - let power_start = (i * 32) + powers_offset; - let power_end = power_start + 32; - let address_start = (i * 32) + eth_addresses_offset; - let address_end = address_start + 32; - - if input.len() < address_end || input.len() < power_end { - return Err(GravityError::ValidationError( - "too short for dynamic data".to_string(), - )); - } - - let power = Uint256::from_bytes_be(&input[power_start..power_end]).unwrap(); - // an eth address at 20 bytes is 12 bytes shorter than the Uint256 it's stored in. - let eth_address = EthAddress::from_slice(&input[address_start + 12..address_end]); - if eth_address.is_err() { - return Err(GravityError::ValidationError( - "Ethereum Address parsing error, probably incorrect parsing".to_string(), - )); - } - let eth_address = eth_address.unwrap(); + for i in 0..validators_powers.len() { + let power = validators_powers[i]; if power > U64_MAX { return Err(GravityError::ValidationError( "Power greater than u64::MAX, probably incorrect parsing".to_string(), )); } let power: u64 = power.to_string().parse().unwrap(); + let eth_address = validators_addresses[i]; validators.push(ValsetMember { power, eth_address }) } - let mut check = validators.clone(); - check.sort(); - check.reverse(); - // if the validator set is not sorted we're in a bad spot - if validators != check { - trace!( - "Someone submitted an unsorted validator set, this means all updates will fail until someone feeds in this unsorted value by hand {:?} instead of {:?}", - validators, check - ); - } Ok(ValsetDataBytes { event_nonce, members: validators, reward_amount, - reward_token, + reward_denom, + reward_recipient, }) } @@ -222,7 +161,8 @@ impl ValsetUpdatedEvent { event_nonce: decoded_bytes.event_nonce, block_height, reward_amount: decoded_bytes.reward_amount, - reward_token: decoded_bytes.reward_token, + reward_denom: decoded_bytes.reward_denom, + reward_recipient: decoded_bytes.reward_recipient, members: decoded_bytes.members, }) } @@ -437,7 +377,14 @@ impl SendToCosmosEvent { "denom length overflow, probably incorrect parsing".to_string(), )); } - let destination_str_len: usize = destination_str_len.to_string().parse().unwrap(); + let destination_str_len: usize = match destination_str_len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; let destination_str_start = 4 * 32; let destination_str_end = destination_str_start + destination_str_len; @@ -611,7 +558,15 @@ impl Erc20DeployedEvent { "denom length overflow, probably incorrect parsing".to_string(), )); } - let denom_len: usize = denom_len.to_string().parse().unwrap(); + let denom_len: usize = match denom_len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + let index_start = 6 * 32; let index_end = index_start + denom_len; let denom = String::from_utf8(data[index_start..index_end].to_vec()); @@ -664,7 +619,15 @@ impl Erc20DeployedEvent { "ERC20 Name length overflow, probably incorrect parsing".to_string(), )); } - let erc20_name_len: usize = erc20_name_len.to_string().parse().unwrap(); + let erc20_name_len: usize = match erc20_name_len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + let index_start = index_end; let index_end = index_start + erc20_name_len; @@ -718,7 +681,15 @@ impl Erc20DeployedEvent { "Symbol length overflow, probably incorrect parsing".to_string(), )); } - let symbol_len: usize = symbol_len.to_string().parse().unwrap(); + let symbol_len: usize = match symbol_len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + let index_start = index_end; let index_end = index_start + symbol_len; @@ -783,6 +754,7 @@ impl Erc20DeployedEvent { ret } } + /// A parsed struct representing the Ethereum event fired when someone uses the Gravity /// contract to deploy a new ERC20 contract representing a Cosmos asset #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq, Hash)] @@ -818,6 +790,151 @@ impl LogicCallExecutedEvent { } } +fn parse_string(data: &[u8], arg_index: usize) -> Result { + // fetching the string from the first 32 bytes + let offset_start = arg_index * 32; + let offset_end = offset_start + 32; + let offset = Uint256::from_bytes_be(&data[offset_start..offset_end]).unwrap(); + + // parse start and end of the string length + let len_start_index = match offset.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + let len_end_index = len_start_index + 32; + + let len = Uint256::from_bytes_be(&data[len_start_index..len_end_index]).unwrap(); + let len: usize = match len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + + if len == 0 { + return Ok("".to_string()); + } + + let start_index = len_end_index; + // based on string length compute how many next bytes will be taken for that string + let end_index = start_index + (((len - 1) / 32) + 1) * 32; + + match String::from_utf8(data[start_index..end_index].to_vec()) { + Ok(s) => Ok(s.trim_matches(char::from(0)).to_string()), + Err(e) => Err(GravityError::ValidationError(format!( + "Can't convert bytes from {:?} to {:?} to string, err: {:?}", + start_index, end_index, e + ))), + } +} + +fn parse_address_array(data: &[u8], arg_index: usize) -> Result, GravityError> { + // fetching the string from the first 32 bytes + let offset_start = arg_index * 32; + let offset_end = offset_start + 32; + let offset = Uint256::from_bytes_be(&data[offset_start..offset_end]).unwrap(); + + // parse start and end of the string length + let len_start_index: usize = match offset.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + + let len_end_index = len_start_index + 32; + + let len = Uint256::from_bytes_be(&data[len_start_index..len_end_index]).unwrap(); + let len: usize = match len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + + let mut list: Vec = Vec::new(); + if len == 0 { + return Ok(list); + } + + for i in 0..len { + let start_index = len_end_index + (32 * i); + let end_index = start_index + 32; + + match EthAddress::from_slice(&data[start_index + 12..end_index]) { + Ok(v) => list.push(v), + Err(e) => { + return Err(GravityError::ValidationError(format!( + "Can't convert bytes from {:?} to {:?} to string, err: {:?}", + start_index, end_index, e + ))); + } + } + } + + Ok(list) +} + +fn parse_uint256_array(data: &[u8], arg_index: usize) -> Result, GravityError> { + // fetching the string from the first 32 bytes + let offset_start = arg_index * 32; + let offset_end = offset_start + 32; + let offset = Uint256::from_bytes_be(&data[offset_start..offset_end]).unwrap(); + + // parse start and end of the string length + let len_start_index: usize = match offset.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + let len_end_index = len_start_index + 32; + + let len = Uint256::from_bytes_be(&data[len_start_index..len_end_index]).unwrap(); + let len: usize = match len.try_resize_to_usize() { + Some(v) => v, + None => { + return Err(GravityError::ValidationError( + "Can't resize to usize".into(), + )) + } + }; + + let mut list: Vec = Vec::new(); + if len == 0 { + return Ok(list); + } + + for i in 0..len { + let start_index = len_end_index + (32 * i); + let end_index = start_index + 32; + + match Uint256::from_bytes_be(&data[start_index..end_index]) { + Some(v) => list.push(v), + None => { + return Err(GravityError::ValidationError(format!( + "Can't convert bytes from {:?} to {:?} to Uint256, values is empty", + start_index, end_index + ))); + } + } + } + + Ok(list) +} + /// Function used for debug printing hex dumps /// of ethereum events with each uint256 on a new /// line @@ -841,7 +958,7 @@ mod tests { use super::*; - const FUZZ_TIMES: u64 = 10_000; + const FUZZ_TIMES: u64 = 1_000; fn get_fuzz_bytes(rng: &mut ThreadRng) -> Vec { let range = Uniform::from(1..200_000); @@ -857,44 +974,52 @@ mod tests { #[test] fn test_valset_decode() { - let event = "0x0000000000000000000000000000000000000000000000000000000000000001\ - 000000000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000a0000000\ - 000000000000000000000000000000000000000000000000000000012000000000\ - 000000000000000000000000000000000000000000000000000000030000000000\ - 000000000000001bb537aa56ffc7d608793baffc6c9c7de3c4f270000000000000\ - 000000000000906313229cfb30959b39a5946099e4526625cbd400000000000000\ - 00000000009f49c7617b72b5784f482bd728d26eba354a0b390000000000000000\ - 000000000000000000000000000000000000000000000003000000000000000000\ - 000000000000000000000000000000000000005555555500000000000000000000\ - 000000000000000000000000000000000000555555550000000000000000000000\ - 000000000000000000000000000000000055555555"; + let event = "0x\ + 0000000000000000000000000000000000000000000000000000000000000002\ + 00000000000000000000000000000000000000000000000000000000004c4b40\ + 00000000000000000000000000000000000000000000000000000000000000c0\ + 0000000000000000000000000000000000000000000000000000000000000100\ + 0000000000000000000000000000000000000000000000000000000000000160\ + 00000000000000000000000000000000000000000000000000000000000001e0\ + 0000000000000000000000000000000000000000000000000000000000000005\ + 7561746f6d000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000002d\ + 636f736d6f73317a6b6c386739766436327830796b767771346d646361656879\ + 6476776338796c683670616e7000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000003\ + 000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6\ + 000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d3\ + 000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4\ + 0000000000000000000000000000000000000000000000000000000000000003\ + 0000000000000000000000000000000000000000000000000000000038e38e36\ + 0000000000000000000000000000000000000000000000000000000038e38e3c\ + 0000000000000000000000000000000000000000000000000000000038e38e39"; + let event_bytes = hex_str_to_bytes(event).unwrap(); let correct = ValsetDataBytes { - event_nonce: 1u8.into(), - reward_amount: u256!(0), - reward_token: None, + event_nonce: 2u8.into(), + reward_amount: u256!(5000000), + reward_denom: "uatom".to_string(), + reward_recipient: "cosmos1zkl8g9vd62x0ykvwq4mdcaehydvwc8ylh6panp".to_string(), members: vec![ ValsetMember { - eth_address: "0x1bb537Aa56fFc7D608793BAFFC6c9C7De3c4F270" + eth_address: "0xc783df8a850f42e7F7e57013759C285caa701eB6" .parse() .unwrap(), - power: 1431655765, + power: 954437174, }, ValsetMember { - eth_address: "0x906313229CFB30959b39A5946099e4526625CBD4" + eth_address: "0xE5904695748fe4A84b40b3fc79De2277660BD1D3" .parse() .unwrap(), - power: 1431655765, + power: 954437180, }, ValsetMember { - eth_address: "0x9F49C7617b72b5784F482Bd728d26EbA354a0B39" + eth_address: "0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4" .parse() .unwrap(), - - power: 1431655765, + power: 954437177, }, ], }; diff --git a/orchestrator/gravity_utils/src/types/event_signatures.rs b/orchestrator/gravity_utils/src/types/event_signatures.rs index abab0bea0..b3781de32 100644 --- a/orchestrator/gravity_utils/src/types/event_signatures.rs +++ b/orchestrator/gravity_utils/src/types/event_signatures.rs @@ -10,4 +10,4 @@ pub const ERC20_DEPLOYED_EVENT_SIG: &str = pub const LOGIC_CALL_EVENT_SIG: &str = "LogicCallEvent(bytes32,uint256,bytes,uint256)"; pub const VALSET_UPDATED_EVENT_SIG: &str = - "ValsetUpdatedEvent(uint256,uint256,uint256,address,address[],uint256[])"; + "ValsetUpdatedEvent(uint256,uint256,uint256,string,string,address[],uint256[])"; diff --git a/orchestrator/gravity_utils/src/types/signatures.rs b/orchestrator/gravity_utils/src/types/signatures.rs index 2c37f4637..1e5b32f37 100644 --- a/orchestrator/gravity_utils/src/types/signatures.rs +++ b/orchestrator/gravity_utils/src/types/signatures.rs @@ -16,20 +16,9 @@ pub struct GravitySignature { } impl Ord for GravitySignature { - // Alex wrote the Go sorting implementation for validator - // sets as Greatest to Least, now this isn't the convention - // for any standard sorting implementation and Rust doesn't - // really like it when you implement sort yourself. It prefers - // Ord. So here we implement Ord with the Eth address sorting - // reversed, since they are also sorted greatest to least in - // the Cosmos module. Then we can call .sort and .reverse and get - // the same sorting as the Cosmos module. + // Sort by eth address asc fn cmp(&self, other: &Self) -> Ordering { - if self.power != other.power { - self.power.cmp(&other.power) - } else { - self.eth_address.cmp(&other.eth_address).reverse() - } + self.eth_address.cmp(&other.eth_address) } } @@ -85,8 +74,8 @@ mod tests { fn test_valset_sort() { let correct: [GravitySignature; 8] = [ GravitySignature { - power: 685294939, - eth_address: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D" + power: 678509841, + eth_address: "0x0A7254b318dd742A3086882321C27779B4B642a6" .parse() .unwrap(), v: u256!(0), @@ -94,8 +83,8 @@ mod tests { s: u256!(0), }, GravitySignature { - power: 678509841, - eth_address: "0x6db48cBBCeD754bDc760720e38E456144e83269b" + power: 685294939, + eth_address: "0x3511A211A6759d48d107898302042d1301187BA9" .parse() .unwrap(), v: u256!(0), @@ -103,8 +92,8 @@ mod tests { s: u256!(0), }, GravitySignature { - power: 671724742, - eth_address: "0x0A7254b318dd742A3086882321C27779B4B642a6" + power: 678509841, + eth_address: "0x37A0603dA2ff6377E5C7f75698dabA8EE4Ba97B8" .parse() .unwrap(), v: u256!(0), @@ -122,7 +111,7 @@ mod tests { }, GravitySignature { power: 671724742, - eth_address: "0x8E91960d704Df3fF24ECAb78AB9df1B5D9144140" + eth_address: "0x479FFc856Cdfa0f5D1AE6Fa61915b01351A7773D" .parse() .unwrap(), v: u256!(0), @@ -131,7 +120,7 @@ mod tests { }, GravitySignature { power: 617443955, - eth_address: "0x3511A211A6759d48d107898302042d1301187BA9" + eth_address: "0xa14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326" .parse() .unwrap(), v: u256!(0), @@ -140,7 +129,7 @@ mod tests { }, GravitySignature { power: 291759231, - eth_address: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326" + eth_address: "0xA24879a175A2F1cEFC7c616f35b6d9c2b0Fd8326" .parse() .unwrap(), v: u256!(0), @@ -149,7 +138,7 @@ mod tests { }, GravitySignature { power: 6785098, - eth_address: "0x37A0603dA2ff6377E5C7f75698dabA8EE4Ba97B8" + eth_address: "0xF14879a175A2F1cEFC7c616f35b6d9c2b0Fd8326" .parse() .unwrap(), v: u256!(0), @@ -164,7 +153,6 @@ mod tests { assert_ne!(incorrect, correct); incorrect.sort(); - incorrect.reverse(); assert_eq!(incorrect, correct); } } diff --git a/orchestrator/gravity_utils/src/types/valsets.rs b/orchestrator/gravity_utils/src/types/valsets.rs index 351571c15..63d130601 100644 --- a/orchestrator/gravity_utils/src/types/valsets.rs +++ b/orchestrator/gravity_utils/src/types/valsets.rs @@ -6,7 +6,7 @@ use std::{ fmt::Debug, }; -use clarity::{constants::ZERO_ADDRESS, u256, Address as EthAddress, Signature as EthSignature}; +use clarity::{u256, Address as EthAddress, Signature as EthSignature}; use deep_space::{error::CosmosGrpcError, Address as CosmosAddress}; use serde::{Deserialize, Serialize}; @@ -81,10 +81,9 @@ pub struct Valset { /// normalized powers for them pub members: Vec, /// An optional reward to be issued to the Relayer on Ethereum - /// TODO make Option pub reward_amount: Uint256, - /// An optional reward to be issued to the Relayer on Ethereum - pub reward_token: Option, + /// An optional reward to be issued to the Relayer on Cosmos + pub reward_denom: String, } impl Valset { @@ -296,17 +295,12 @@ impl From for Valset { impl From<&gravity_proto::gravity::Valset> for Valset { fn from(input: &gravity_proto::gravity::Valset) -> Self { - let parsed_reward_token = input.reward_token.parse().unwrap(); - let reward_token = if parsed_reward_token == ZERO_ADDRESS { - None - } else { - Some(parsed_reward_token) - }; + let reward_denom = input.reward_denom.clone(); Valset { nonce: input.nonce, members: input.members.iter().map(|i| i.into()).collect(), reward_amount: Uint256::from_dec_or_hex_str_restricted(&input.reward_amount).unwrap(), - reward_token, + reward_denom, } } } @@ -320,20 +314,9 @@ pub struct ValsetMember { } impl Ord for ValsetMember { - // Alex wrote the Go sorting implementation for validator - // sets as Greatest to Least, now this isn't the convention - // for any standard sorting implementation and Rust doesn't - // really like it when you implement sort yourself. It prefers - // Ord. So here we implement Ord with the Eth address sorting - // reversed, since they are also sorted greatest to least in - // the Cosmos module. Then we can call .sort and .reverse and get - // the same sorting as the Cosmos module. + // Sort by the eth_address asc fn cmp(&self, other: &Self) -> Ordering { - if self.power != other.power { - self.power.cmp(&other.power) - } else { - self.eth_address.cmp(&other.eth_address).reverse() - } + self.eth_address.cmp(&other.eth_address) } } @@ -424,7 +407,7 @@ impl Into for &Valset { height: 0, members: self.members.iter().map(|v| v.into()).collect(), reward_amount: self.reward_amount.to_string(), - reward_token: self.reward_token.unwrap_or(ZERO_ADDRESS).to_string(), + reward_denom: self.reward_denom.to_string(), } } } diff --git a/orchestrator/orchestrator/Cargo.toml b/orchestrator/orchestrator/Cargo.toml index 1ff9d7007..d0baff94d 100644 --- a/orchestrator/orchestrator/Cargo.toml +++ b/orchestrator/orchestrator/Cargo.toml @@ -14,7 +14,7 @@ ethereum_gravity = { path = "../ethereum_gravity" } gravity_proto = { path = "../gravity_proto" } gravity_utils = { path = "../gravity_utils" } metrics_exporter = { path = "../metrics_exporter" } -relayer = { path = "../relayer/" } +relayer = { path = "../relayer" } futures = "0.3" lazy_static = "1" diff --git a/orchestrator/orchestrator/src/main_loop.rs b/orchestrator/orchestrator/src/main_loop.rs index 04be70a03..9e76e92bc 100644 --- a/orchestrator/orchestrator/src/main_loop.rs +++ b/orchestrator/orchestrator/src/main_loop.rs @@ -19,7 +19,7 @@ use gravity_proto::{ use gravity_utils::{ clarity::{address::Address as EthAddress, u256, PrivateKey as EthPrivateKey, Uint256}, deep_space::{ - client::ChainStatus, coin::Coin, error::CosmosGrpcError, + address::Address as CosmosAddress, client::ChainStatus, coin::Coin, error::CosmosGrpcError, private_key::PrivateKey as CosmosPrivateKey, utils::FeeInfo, Contact, }, error::GravityError, @@ -76,6 +76,10 @@ pub async fn orchestrator_main_loop( fee.clone(), ); + // TODO add additional arg param to override it + // by default the address will be taken from the cosmos_key + let reward_recipient: CosmosAddress = cosmos_key.to_address(&contact.get_prefix()).unwrap(); + let c = relayer_main_loop( ethereum_key, Some(cosmos_key), @@ -86,6 +90,7 @@ pub async fn orchestrator_main_loop( gravity_contract_address, gravity_id, &config.relayer, + reward_recipient, ); // if the relayer is not enabled we just don't start the future diff --git a/orchestrator/relayer/src/find_latest_valset.rs b/orchestrator/relayer/src/find_latest_valset.rs index cc6b00a3f..26daeacf6 100644 --- a/orchestrator/relayer/src/find_latest_valset.rs +++ b/orchestrator/relayer/src/find_latest_valset.rs @@ -52,7 +52,7 @@ pub async fn find_latest_valset( nonce: event.valset_nonce, members: event.members, reward_amount: event.reward_amount, - reward_token: event.reward_token, + reward_denom: event.reward_denom, }; let cosmos_chain_valset = cosmos_gravity::query::get_valset(grpc_client, latest_eth_valset.nonce) @@ -92,7 +92,7 @@ fn check_if_valsets_differ(cosmos_valset: Option, ethereum_valset: &Vals // or with our Ethereum search assert_eq!(cosmos_valset.nonce, ethereum_valset.nonce); - let mut c_valset = cosmos_valset.members; + let mut c_valset = cosmos_valset.members.clone(); let mut e_valset = ethereum_valset.members.clone(); c_valset.sort(); e_valset.sort(); @@ -104,5 +104,9 @@ fn check_if_valsets_differ(cosmos_valset: Option, ethereum_valset: &Vals } else { info!("Validator sets for nonce {} Cosmos and Ethereum differ. Possible bridge highjacking!", ethereum_valset.nonce) } + info!( + "\n cosmos valset: {:?}, \n ethereum valset: {:?}", + cosmos_valset, *ethereum_valset + ); } } diff --git a/orchestrator/relayer/src/main_loop.rs b/orchestrator/relayer/src/main_loop.rs index 0d8136c7f..1477bd9a5 100644 --- a/orchestrator/relayer/src/main_loop.rs +++ b/orchestrator/relayer/src/main_loop.rs @@ -3,7 +3,9 @@ use std::time::Duration; use gravity_proto::gravity::query_client::QueryClient as GravityQueryClient; use gravity_utils::{ clarity::{address::Address as EthAddress, PrivateKey as EthPrivateKey}, - deep_space::{Coin, Contact, PrivateKey as CosmosPrivateKey}, + deep_space::{ + address::Address as CosmosAddress, Coin, Contact, PrivateKey as CosmosPrivateKey, + }, error::GravityError, types::RelayerConfig, web30::client::Web3, @@ -32,6 +34,7 @@ pub async fn relayer_main_loop( gravity_contract_address: EthAddress, gravity_id: String, relayer_config: &RelayerConfig, + reward_recipient: CosmosAddress, ) -> Result<(), GravityError> { let mut grpc_client = grpc_client; let loop_speed = Duration::from_secs(relayer_config.relayer_loop_speed); @@ -57,6 +60,7 @@ pub async fn relayer_main_loop( gravity_id.clone(), TIMEOUT, relayer_config, + reward_recipient, ) .await; diff --git a/orchestrator/relayer/src/valset_relaying.rs b/orchestrator/relayer/src/valset_relaying.rs index b2de2c493..8f6dece61 100644 --- a/orchestrator/relayer/src/valset_relaying.rs +++ b/orchestrator/relayer/src/valset_relaying.rs @@ -11,16 +11,14 @@ use ethereum_gravity::{ use gravity_proto::gravity::query_client::QueryClient as GravityQueryClient; use gravity_utils::{ clarity::{address::Address as EthAddress, PrivateKey as EthPrivateKey}, + deep_space::address::Address as CosmosAddress, error::GravityError, num_conversion::{print_eth, print_gwei}, - prices::get_weth_price, types::{RelayerConfig, Valset, ValsetConfirmResponse, ValsetRelayingMode}, web30::client::Web3, }; use tonic::transport::Channel; -use crate::batch_relaying::get_cost_with_margin; - #[allow(clippy::too_many_arguments)] /// High level entry point for valset relaying, this function starts by finding /// what validator set is valid, then evaluating if it should be relayed according @@ -36,6 +34,7 @@ pub async fn relay_valsets( gravity_id: String, timeout: Duration, config: &RelayerConfig, + reward_recipient: CosmosAddress, ) { // we have to start with the current valset, we need to know what's currently // in the contract in order to determine if a new validator set is valid. @@ -83,6 +82,7 @@ pub async fn relay_valsets( ethereum_key, timeout, config, + reward_recipient, ) .await; } @@ -100,6 +100,7 @@ async fn relay_valid_valset( ethereum_key: EthPrivateKey, timeout: Duration, config: &RelayerConfig, + reward_recipient: CosmosAddress, ) { let cost = ethereum_gravity::valset_update::estimate_valset_cost( valset_to_relay, @@ -109,6 +110,7 @@ async fn relay_valid_valset( gravity_contract_address, gravity_id.clone(), ethereum_key, + reward_recipient, ) .await; if cost.is_err() { @@ -136,9 +138,6 @@ async fn relay_valid_valset( let should_relay = should_relay_valset( latest_cosmos_valset_nonce, valset_to_relay, - ethereum_key.to_address(), - cost, - web3, &config.valset_relaying_mode, ) .await; @@ -153,6 +152,7 @@ async fn relay_valid_valset( gravity_contract_address, gravity_id, ethereum_key, + reward_recipient, ) .await; } else { @@ -230,34 +230,9 @@ async fn find_latest_valid_valset( async fn should_relay_valset( latest_cosmos_valset_nonce: u64, valset: &Valset, - pubkey: EthAddress, - cost: GasCost, - web3: &Web3, config: &ValsetRelayingMode, ) -> bool { match config { - // if the user has configured only profitable relaying then it is our only consideration - ValsetRelayingMode::ProfitableOnly { margin } => match valset.reward_token { - Some(reward_token) => { - let price = get_weth_price(reward_token, valset.reward_amount, pubkey, web3).await; - let cost_with_margin = get_cost_with_margin(cost.get_total(), *margin); - // we need to see how much WETH we can get for the reward token amount, - // and compare that value to the gas cost times the margin - match price { - Ok(price) => price > cost_with_margin, - Err(e) => { - info!( - "Unable to determine swap price of token {} for WETH \n - it may just not be on Uniswap - Will not be relaying valset {:?}", - reward_token, e - ); - false - } - } - } - None => false, - }, - // if the user has requested to relay every single valset, we do so ValsetRelayingMode::EveryValset => true, // user is an altruistic relayer, so we'll do our best to balance not spending diff --git a/orchestrator/test_runner/src/evidence_based_slashing.rs b/orchestrator/test_runner/src/evidence_based_slashing.rs index 0590f94ae..f2991e213 100644 --- a/orchestrator/test_runner/src/evidence_based_slashing.rs +++ b/orchestrator/test_runner/src/evidence_based_slashing.rs @@ -50,7 +50,7 @@ pub async fn evidence_based_slashing( eth_address: eth_addr, }], reward_amount: u256!(0), - reward_token: None, + reward_denom: "".to_string(), }; let gravity_id = get_gravity_id(gravity_address, eth_addr, web30) .await diff --git a/orchestrator/test_runner/src/happy_path_v2.rs b/orchestrator/test_runner/src/happy_path_v2.rs index bb4bc8b28..81f999640 100644 --- a/orchestrator/test_runner/src/happy_path_v2.rs +++ b/orchestrator/test_runner/src/happy_path_v2.rs @@ -58,38 +58,42 @@ pub async fn happy_path_test_v2( denom: token_to_send_to_eth.clone(), amount: amount_to_bridge, }; + let send_to_eth_coin_fee = Coin { + denom: token_to_send_to_eth.clone(), + amount: u256!(1), + }; let user = get_user_key(); - // send the user some footoken - contact - .send_coins( - send_to_user_coin.clone(), - Some(get_fee()), - user.cosmos_address, - Some(TOTAL_TIMEOUT), - keys[0].validator_key, - ) - .await - .unwrap(); - let balances = contact.get_balances(user.cosmos_address).await.unwrap(); - let mut found = false; - for coin in balances { - if coin.denom == token_to_send_to_eth.clone() { - found = true; - break; - } - } - if !found { - panic!( - "Failed to send {} to the user address", - token_to_send_to_eth + // send the foo token to bridge and stake token to pay for cosmos fee + for coin in &vec![send_to_user_coin.clone(), get_fee()] { + info!( + "Sending {} to the test user, address: {}", + coin.clone(), + user.cosmos_address ); + + contact + .send_coins( + coin.clone(), + Some(get_fee()), + user.cosmos_address, + Some(TOTAL_TIMEOUT), + keys[0].validator_key, + ) + .await + .unwrap(); + + let balance = contact + .get_balance(user.cosmos_address, coin.clone().denom) + .await + .unwrap(); + if balance.is_none() { + panic!("Failed to send {} to the user address", coin); + } + info!("Sent {} to user address {}", coin, user.cosmos_address); } - info!( - "Sent some {} to user address {}", - token_to_send_to_eth, user.cosmos_address - ); + // send the user some eth, they only need this to check their // erc20 balance, so a pretty minor usecase send_one_eth(user.eth_address, web30).await; @@ -99,7 +103,7 @@ pub async fn happy_path_test_v2( user.cosmos_key, user.eth_address, send_to_eth_coin, - get_fee(), + send_to_eth_coin_fee, get_fee(), contact, ) diff --git a/orchestrator/test_runner/src/main.rs b/orchestrator/test_runner/src/main.rs index d4a216665..7ce5c9af1 100644 --- a/orchestrator/test_runner/src/main.rs +++ b/orchestrator/test_runner/src/main.rs @@ -93,10 +93,7 @@ lazy_static! { static ref MINER_ADDRESS: EthAddress = MINER_PRIVATE_KEY.to_address(); } -/// Gets the standard non-token fee for the testnet. We deploy the test chain with STAKE -/// and FOOTOKEN balances by default, one footoken is sufficient for any Cosmos tx fee except -/// fees for send_to_eth messages which have to be of the same bridged denom so that the relayers -/// on the Ethereum side can be paid in that token. +/// returns the static fee for the tests pub fn get_fee() -> Coin { Coin { denom: get_test_token_name(), @@ -110,8 +107,9 @@ pub fn get_deposit() -> Coin { amount: u256!(1000000000000000000), // 10^18 } } + pub fn get_test_token_name() -> String { - "footoken".to_string() + "stake".to_string() } pub fn get_chain_id() -> String { diff --git a/orchestrator/test_runner/src/utils.rs b/orchestrator/test_runner/src/utils.rs index 842b448cb..026f6346c 100644 --- a/orchestrator/test_runner/src/utils.rs +++ b/orchestrator/test_runner/src/utils.rs @@ -526,7 +526,7 @@ pub async fn get_event_nonce_safe( web3: &Web3, caller_address: EthAddress, ) -> Result { - return tokio::time::timeout(TOTAL_TIMEOUT, async { + tokio::time::timeout(TOTAL_TIMEOUT, async { loop { let new_balance = get_event_nonce(gravity_contract_address, caller_address, web3).await; if let Err(ref e) = new_balance { @@ -538,7 +538,7 @@ pub async fn get_event_nonce_safe( } }) .await - .expect("Can't get event nonce withing timeout"); + .expect("Can't get event nonce withing timeout") } /// waits for the cosmos chain to start producing blocks, used to prevent race conditions diff --git a/orchestrator/test_runner/src/valset_rewards.rs b/orchestrator/test_runner/src/valset_rewards.rs index 79a73f905..34930d232 100644 --- a/orchestrator/test_runner/src/valset_rewards.rs +++ b/orchestrator/test_runner/src/valset_rewards.rs @@ -1,26 +1,29 @@ //! This is a test for validator set relaying rewards +use std::{collections::HashMap, time::Duration}; + use cosmos_gravity::query::get_gravity_params; use gravity_proto::{ cosmos_sdk_proto::cosmos::params::v1beta1::ParamChange, gravity::query_client::QueryClient as GravityQueryClient, }; use gravity_utils::{ - clarity::{u256, Address as EthAddress}, - deep_space::{coin::Coin, Contact}, + clarity::{u256, Address as EthAddress, Uint256}, + deep_space::{coin::Coin, Address as CosmosAddress, Contact}, u64_array_bigints, web30::client::Web3, }; +use tokio::time::{sleep, timeout}; use tonic::transport::Channel; use crate::{ airdrop_proposal::wait_for_proposals_to_execute, happy_path::test_valset_update, - happy_path_v2::deploy_cosmos_representing_erc20_and_check_adoption, utils::{ - create_parameter_change_proposal, footoken_metadata, get_erc20_balance_safe, - vote_yes_on_proposals, ValidatorKeys, + create_default_test_config, create_parameter_change_proposal, footoken_metadata, + start_orchestrators, vote_yes_on_proposals, ValidatorKeys, }, + TOTAL_TIMEOUT, }; pub async fn valset_rewards_test( @@ -33,21 +36,11 @@ pub async fn valset_rewards_test( let mut grpc_client = grpc_client; let token_to_send_to_eth = footoken_metadata(contact).await.base; - // first we deploy the Cosmos asset that we will use as a reward and make sure it is adopted - // by the Cosmos chain - let erc20_contract = deploy_cosmos_representing_erc20_and_check_adoption( - gravity_address, - web30, - Some(keys.clone()), - &mut grpc_client, - false, - footoken_metadata(contact).await, - ) - .await; - - // reward of 1 mfootoken + let no_relay_market_config = create_default_test_config(); + start_orchestrators(keys.clone(), gravity_address, false, no_relay_market_config).await; + let valset_reward = Coin { - denom: token_to_send_to_eth, + denom: token_to_send_to_eth.clone(), amount: u256!(1_000_000), }; @@ -74,7 +67,7 @@ pub async fn valset_rewards_test( // next we create a governance proposal to use the newly bridged asset as the reward // and vote to pass the proposal - info!("Creating parameter change governance proposal"); + info!("Creating parameter change governance proposal."); create_parameter_change_proposal(contact, keys[0].validator_key, params_to_change).await; vote_yes_on_proposals(contact, &keys, None).await; @@ -87,22 +80,63 @@ pub async fn valset_rewards_test( assert_eq!(params.bridge_chain_id, 1); assert_eq!(params.bridge_ethereum_address, gravity_address.to_string()); - // trigger a valset update - test_valset_update(web30, contact, &mut grpc_client, &keys, gravity_address).await; - - // check that one of the relayers has footoken now - let mut found = false; + // capture the foo token balance before the valset update + let mut inital_reward_token_balance: HashMap = HashMap::new(); for key in keys.iter() { - let target_address = key.eth_key.to_address(); - let balance_of_footoken = get_erc20_balance_safe(erc20_contract, web30, target_address) + let orch_address = key.orch_key.to_address(&contact.get_prefix()).unwrap(); + let balance = contact + .get_balance(orch_address, token_to_send_to_eth.to_string()) .await .unwrap(); - if balance_of_footoken == valset_reward.amount { - found = true; + if balance.is_none() { + continue; } + inital_reward_token_balance.insert(orch_address, balance.unwrap().amount); } - if !found { - panic!("No relayer was rewarded in footoken for relaying validator set!") + + info!("Trigger a valset update."); + test_valset_update(web30, contact, &mut grpc_client, &keys, gravity_address).await; + + let check_valset_reward_deposit = async { + loop { + let mut found = false; + for key in keys.iter() { + let orch_address = key.orch_key.to_address(&contact.get_prefix()).unwrap(); + let balance = contact + .get_balance(orch_address, token_to_send_to_eth.to_string()) + .await + .unwrap(); + if balance.is_none() { + continue; + } + let balance = balance.unwrap().amount; + let initial_balance = inital_reward_token_balance.get(&orch_address); + if initial_balance.is_none() { + continue; + } + let initial_balance = *initial_balance.unwrap(); + if initial_balance.checked_add(valset_reward.amount).unwrap() == balance { + info!("Found increased valset update reward of the, orch {}, initial balance: {}, reward: {}, new balance :{}!", + orch_address, initial_balance, valset_reward.amount, balance); + found = true; + } + } + + if found { + break; + } + + sleep(Duration::from_secs(5)).await; + } + }; + + info!("Waiting for valset reward deposit."); + if timeout(TOTAL_TIMEOUT, check_valset_reward_deposit) + .await + .is_err() + { + panic!("Failed to perform the valset reward deposits to Cosmos!"); } - info!("Successfully Issued validator set reward!"); + + info!("Successfully issued validator set reward!"); } diff --git a/solidity/contracts/CosmosToken.sol b/solidity/contracts/CosmosToken.sol index 61dd32732..2d6ef4d9c 100644 --- a/solidity/contracts/CosmosToken.sol +++ b/solidity/contracts/CosmosToken.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract CosmosERC20 is ERC20 { diff --git a/solidity/contracts/Gravity.sol b/solidity/contracts/Gravity.sol index 7e99fbe8e..e8ca585cb 100644 --- a/solidity/contracts/Gravity.sol +++ b/solidity/contracts/Gravity.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -16,6 +16,7 @@ error InvalidLogicCallTransfers(); error InvalidLogicCallFees(); error InvalidSendToCosmos(); error IncorrectCheckpoint(); +error IncorrectRewardRecipient(); error MalformedNewValidatorSet(); error MalformedCurrentValidatorSet(); error MalformedBatch(); @@ -56,8 +57,8 @@ struct ValsetArgs { // the reward amount denominated in the below reward token, can be // set to zero uint256 rewardAmount; - // the reward token, should be set to the zero address if not being used - address rewardToken; + // the cosmos reward denom/token, should be set to the empty if not being used + string rewardDenom; } // This represents a validator signature @@ -121,7 +122,8 @@ contract Gravity is ReentrancyGuard { uint256 indexed _newValsetNonce, uint256 _eventNonce, uint256 _rewardAmount, - address _rewardToken, + string _rewardDenom, + string _rewardRecipient, address[] _validators, uint256[] _powers ); @@ -214,7 +216,7 @@ contract Gravity is ReentrancyGuard { _valsetArgs.validators, _valsetArgs.powers, _valsetArgs.rewardAmount, - _valsetArgs.rewardToken + _valsetArgs.rewardDenom ) ); @@ -269,7 +271,9 @@ contract Gravity is ReentrancyGuard { // The current validators that approve the change ValsetArgs calldata _currentValset, // These are arrays of the parts of the current validator's signatures - Signature[] calldata _sigs + Signature[] calldata _sigs, + // The cosmos originated address of the reward recipient, might be empty + string calldata rewardRecipient ) external { // CHECKS @@ -331,6 +335,11 @@ contract Gravity is ReentrancyGuard { checkValidatorSignatures(_currentValset, _sigs, newCheckpoint, constant_powerThreshold); + // The reward recipient must be present if the reward is present + if (bytes(_newValset.rewardDenom).length != 0 && _newValset.rewardAmount != 0 && bytes(rewardRecipient).length == 0) { + revert IncorrectRewardRecipient(); + } + // ACTIONS // Stored to be used next time to validate that the valset @@ -340,11 +349,6 @@ contract Gravity is ReentrancyGuard { // Store new nonce state_lastValsetNonce = _newValset.valsetNonce; - // Send submission reward to msg.sender if reward token is a valid value - if (_newValset.rewardToken != address(0) && _newValset.rewardAmount != 0) { - IERC20(_newValset.rewardToken).safeTransfer(msg.sender, _newValset.rewardAmount); - } - // LOGS state_lastEventNonce = state_lastEventNonce + 1; @@ -352,7 +356,8 @@ contract Gravity is ReentrancyGuard { _newValset.valsetNonce, state_lastEventNonce, _newValset.rewardAmount, - _newValset.rewardToken, + _newValset.rewardDenom, + rewardRecipient, _newValset.validators, _newValset.powers ); @@ -674,7 +679,7 @@ contract Gravity is ReentrancyGuard { } ValsetArgs memory _valset; - _valset = ValsetArgs(_validators, _powers, 0, 0, address(0)); + _valset = ValsetArgs(_validators, _powers, 0, 0, ""); bytes32 newCheckpoint = makeCheckpoint(_valset, _gravityId); @@ -689,7 +694,8 @@ contract Gravity is ReentrancyGuard { state_lastValsetNonce, state_lastEventNonce, 0, - address(0), + "", + "", _validators, _powers ); diff --git a/solidity/contracts/HashingTest.sol b/solidity/contracts/HashingTest.sol index 35d727138..25de017d9 100644 --- a/solidity/contracts/HashingTest.sol +++ b/solidity/contracts/HashingTest.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "hardhat/console.sol"; diff --git a/solidity/contracts/ReentrantERC20.sol b/solidity/contracts/ReentrantERC20.sol index 6b448d36b..bd044474e 100644 --- a/solidity/contracts/ReentrantERC20.sol +++ b/solidity/contracts/ReentrantERC20.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "./Gravity.sol"; @@ -17,7 +17,6 @@ contract ReentrantERC20 { address[] memory addresses = new address[](0); Signature[] memory sigs = new Signature[](0); uint256[] memory uint256s = new uint256[](0); - address blankAddress = address(0); bytes memory bytess = new bytes(0); uint256 zero = 0; LogicCallArgs memory args; @@ -38,7 +37,7 @@ contract ReentrantERC20 { } { - valset = ValsetArgs(addresses, uint256s, zero, zero, blankAddress); + valset = ValsetArgs(addresses, uint256s, zero, zero, ""); } Gravity(state_gravityAddress).submitLogicCall(valset, sigs, args); diff --git a/solidity/contracts/SigningTest.sol b/solidity/contracts/SigningTest.sol index dae7feec8..3ddc15423 100644 --- a/solidity/contracts/SigningTest.sol +++ b/solidity/contracts/SigningTest.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "hardhat/console.sol"; diff --git a/solidity/contracts/SimpleLogicBatch.sol b/solidity/contracts/SimpleLogicBatch.sol index 3cd8d765c..cb3d2e9ae 100644 --- a/solidity/contracts/SimpleLogicBatch.sol +++ b/solidity/contracts/SimpleLogicBatch.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/solidity/contracts/TestERC20A.sol b/solidity/contracts/TestERC20A.sol index 9bb89032f..7206d606d 100644 --- a/solidity/contracts/TestERC20A.sol +++ b/solidity/contracts/TestERC20A.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // One of three testing coins diff --git a/solidity/contracts/TestERC20B.sol b/solidity/contracts/TestERC20B.sol index b553391a3..e2d9a233c 100644 --- a/solidity/contracts/TestERC20B.sol +++ b/solidity/contracts/TestERC20B.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/solidity/contracts/TestERC20BNOM.sol b/solidity/contracts/TestERC20BNOM.sol index 1df839d63..0f8f958d3 100644 --- a/solidity/contracts/TestERC20BNOM.sol +++ b/solidity/contracts/TestERC20BNOM.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; diff --git a/solidity/contracts/TestERC20C.sol b/solidity/contracts/TestERC20C.sol index 119977b89..2070e37d9 100644 --- a/solidity/contracts/TestERC20C.sol +++ b/solidity/contracts/TestERC20C.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.10; +pragma solidity 0.8.15; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/solidity/contracts/TestLogicContract.sol b/solidity/contracts/TestLogicContract.sol index 74c322c9d..aebd9ac78 100644 --- a/solidity/contracts/TestLogicContract.sol +++ b/solidity/contracts/TestLogicContract.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.10; +pragma solidity ^0.8.15; import "hardhat/console.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/solidity/contracts/TestTokenBatchMiddleware.sol b/solidity/contracts/TestTokenBatchMiddleware.sol index fdfaaa321..019dbfac0 100644 --- a/solidity/contracts/TestTokenBatchMiddleware.sol +++ b/solidity/contracts/TestTokenBatchMiddleware.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.10; +pragma solidity ^0.8.15; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/solidity/hardhat.config.ts b/solidity/hardhat.config.ts index 067c36b48..3f59e930a 100644 --- a/solidity/hardhat.config.ts +++ b/solidity/hardhat.config.ts @@ -28,8 +28,9 @@ task("accounts", "Prints the list of accounts", async (args, hre) => { module.exports = { // This is a sample solc configuration that specifies which version of solc to use solidity: { - version: "0.8.10", + version: "0.8.15", settings: { + viaIR: true, optimizer: { enabled: true } diff --git a/solidity/test-utils/index.ts b/solidity/test-utils/index.ts index 060b6ebdc..926361bdc 100644 --- a/solidity/test-utils/index.ts +++ b/solidity/test-utils/index.ts @@ -2,7 +2,7 @@ import { Gravity } from "../typechain/Gravity"; import { TestERC20A } from "../typechain/TestERC20A"; import { TestERC20BNOM } from "../typechain/TestERC20BNOM"; import { ethers } from "hardhat"; -import { makeCheckpoint, getSignerAddresses, ZeroAddress } from "./pure"; +import { makeCheckpoint, getSignerAddresses, EmptyDenom } from "./pure"; import { Signer } from "ethers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; @@ -29,7 +29,7 @@ export async function deployContracts( const valAddresses = await getSignerAddresses(validators); - const checkpoint = makeCheckpoint(valAddresses, powers, 0, 0, ZeroAddress, gravityId); + const checkpoint = makeCheckpoint(valAddresses, powers, 0, 0, EmptyDenom, gravityId); const gravity = (await Gravity.deploy( gravityId, diff --git a/solidity/test-utils/pure.ts b/solidity/test-utils/pure.ts index 88f0f7b21..6994b5744 100644 --- a/solidity/test-utils/pure.ts +++ b/solidity/test-utils/pure.ts @@ -3,39 +3,37 @@ import { BigNumberish } from "ethers"; import { Signer } from "ethers"; import { ContractTransaction, utils } from 'ethers'; -export const ZeroAddress: string = "0x0000000000000000000000000000000000000000" +export const EmptyDenom: string = "" +export const EmptyCosmosAddress: string = "" + +export type Sig = { + v: number, + r: string, + s: string +}; export async function getSignerAddresses(signers: Signer[]) { return await Promise.all(signers.map(signer => signer.getAddress())); } - export function makeCheckpoint( validators: string[], powers: BigNumberish[], valsetNonce: BigNumberish, rewardAmount: BigNumberish, - rewardToken: string, + rewardDenom: string, gravityId: string ) { const methodName = ethers.utils.formatBytes32String("checkpoint"); let abiEncoded = ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "uint256", "address[]", "uint256[]", "uint256", "address"], - [gravityId, methodName, valsetNonce, validators, powers, rewardAmount, rewardToken] + ["bytes32", "bytes32", "uint256", "address[]", "uint256[]", "uint256", "string"], + [gravityId, methodName, valsetNonce, validators, powers, rewardAmount, rewardDenom] ); - let checkpoint = ethers.utils.keccak256(abiEncoded); - - return checkpoint; + return ethers.utils.keccak256(abiEncoded);; } -export type Sig = { - v: number, - r: string, - s: string -}; - export async function signHash(signers: Signer[], hash: string) { let sigs: Sig[] = []; @@ -50,27 +48,6 @@ export async function signHash(signers: Signer[], hash: string) { return sigs; } -export function makeTxBatchHash( - amounts: number[], - destinations: string[], - fees: number[], - nonces: number[], - gravityId: string -) { - const methodName = ethers.utils.formatBytes32String("transactionBatch"); - - let abiEncoded = ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "uint256[]", "address[]", "uint256[]", "uint256[]"], - [gravityId, methodName, amounts, destinations, fees, nonces] - ); - - // console.log(abiEncoded); - - let txHash = ethers.utils.keccak256(abiEncoded); - - return txHash; -} - export async function parseEvent(contract: any, txPromise: Promise, eventOrder: number) { const tx = await txPromise const receipt = await contract.provider.getTransactionReceipt(tx.hash!) diff --git a/solidity/test/arbitrary-logic.ts b/solidity/test/arbitrary-logic.ts index dd4526d9d..a15d771f7 100644 --- a/solidity/test/arbitrary-logic.ts +++ b/solidity/test/arbitrary-logic.ts @@ -5,7 +5,7 @@ import {TestLogicContract} from "../typechain/TestLogicContract"; import {SimpleLogicBatchMiddleware} from "../typechain/SimpleLogicBatchMiddleware"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, signHash, ZeroAddress} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, signHash, EmptyDenom} from "../test-utils/pure"; import {MintedForDeployer} from "./deployERC20"; chai.use(solidity); @@ -200,7 +200,7 @@ async function runTest(opts: { powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } let logicCallSubmitResult = await gravity.submitLogicCall( @@ -381,7 +381,7 @@ describe("logicCall Go test hash", function () { powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } var res = await gravity.populateTransaction.submitLogicCall( diff --git a/solidity/test/deployERC20.ts b/solidity/test/deployERC20.ts index 330379cda..cbbe471aa 100644 --- a/solidity/test/deployERC20.ts +++ b/solidity/test/deployERC20.ts @@ -3,7 +3,7 @@ import {ethers} from "hardhat"; import {solidity} from "ethereum-waffle"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, parseEvent, signHash, ZeroAddress,} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, parseEvent, signHash, EmptyDenom,} from "../test-utils/pure"; import {BigNumber} from "ethers"; chai.use(solidity); @@ -109,7 +109,7 @@ async function runTest(opts: { duplicateValidator?: boolean; sortValidators?: bo powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } await gravity.submitBatch( diff --git a/solidity/test/gas-test.ts b/solidity/test/gas-test.ts index d9444d705..8d89482f6 100644 --- a/solidity/test/gas-test.ts +++ b/solidity/test/gas-test.ts @@ -3,7 +3,7 @@ import {ethers} from "hardhat"; import {solidity} from "ethereum-waffle"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, signHash, ZeroAddress} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, signHash, EmptyDenom} from "../test-utils/pure"; chai.use(solidity); @@ -27,7 +27,7 @@ describe("Gas tests", function () { powers, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } await gravity.testMakeCheckpoint( @@ -61,7 +61,7 @@ describe("Gas tests", function () { powers: powers, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom }; await gravity.testCheckValidatorSignatures( v, diff --git a/solidity/test/happy-path.ts b/solidity/test/happy-path.ts index 8c365bf44..094ebaabb 100644 --- a/solidity/test/happy-path.ts +++ b/solidity/test/happy-path.ts @@ -3,7 +3,7 @@ import {ethers} from "hardhat"; import {solidity} from "ethereum-waffle"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, makeCheckpoint, signHash, ZeroAddress,} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, makeCheckpoint, signHash, EmptyDenom,} from "../test-utils/pure"; import {MintedForDeployer} from "./deployERC20"; chai.use(solidity); @@ -26,7 +26,7 @@ describe("Gravity happy path valset update + batch submit", function () { validators, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } @@ -52,7 +52,7 @@ describe("Gravity happy path valset update + batch submit", function () { validators: validators, valsetNonce: 1, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } })() @@ -62,14 +62,14 @@ describe("Gravity happy path valset update + batch submit", function () { validators: await getSignerAddresses(valset0.validators), valsetNonce: valset0.valsetNonce, rewardAmount: valset0.rewardAmount, - rewardToken: valset0.rewardToken + rewardDenom: valset0.rewardDenom } const valset1_str = { powers: valset1.powers, validators: await getSignerAddresses(valset1.validators), valsetNonce: valset1.valsetNonce, rewardAmount: valset1.rewardAmount, - rewardToken: valset1.rewardToken + rewardDenom: valset1.rewardDenom } const checkpoint1 = makeCheckpoint( @@ -77,7 +77,7 @@ describe("Gravity happy path valset update + batch submit", function () { valset1_str.powers, valset1_str.valsetNonce, valset1_str.rewardAmount, - valset1_str.rewardToken, + valset1_str.rewardDenom, gravityId ); @@ -87,6 +87,7 @@ describe("Gravity happy path valset update + batch submit", function () { valset1_str, valset0_str, sigs1, + "" ); expect((await gravity.functions.state_lastValsetCheckpoint())[0]).to.equal(checkpoint1); diff --git a/solidity/test/submitBatch-vs-logicCall.ts b/solidity/test/submitBatch-vs-logicCall.ts index d66f1d662..3f6c3850c 100644 --- a/solidity/test/submitBatch-vs-logicCall.ts +++ b/solidity/test/submitBatch-vs-logicCall.ts @@ -4,7 +4,7 @@ import {solidity} from "ethereum-waffle"; import {TestTokenBatchMiddleware} from "../typechain/TestTokenBatchMiddleware"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, signHash, ZeroAddress,} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, signHash, EmptyDenom,} from "../test-utils/pure"; import {Signer} from "ethers"; import {Gravity} from "../typechain/Gravity"; import {TestERC20A} from "../typechain/TestERC20A"; @@ -151,7 +151,7 @@ async function runSubmitBatchTest(opts: { batchSize: number }) { powers, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } await gravity.submitBatch( @@ -297,7 +297,7 @@ async function runLogicCallTest(opts: { powers, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } await gravity.submitLogicCall( diff --git a/solidity/test/submitBatch.ts b/solidity/test/submitBatch.ts index e09ad2034..546f6ca4e 100644 --- a/solidity/test/submitBatch.ts +++ b/solidity/test/submitBatch.ts @@ -3,7 +3,7 @@ import {ethers} from "hardhat"; import {solidity} from "ethereum-waffle"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, signHash, ZeroAddress,} from "../test-utils/pure"; +import {examplePowers, getSignerAddresses, signHash, EmptyDenom,} from "../test-utils/pure"; chai.use(solidity); const {expect} = chai; @@ -150,7 +150,7 @@ async function runTest(opts: { powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } let batchSubmitTx = await gravity.submitBatch( @@ -304,7 +304,7 @@ describe("submitBatch Go test hash", function () { powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } await gravity.submitBatch( diff --git a/solidity/test/updateValset.ts b/solidity/test/updateValset.ts index 3637124ae..1da2a08ea 100644 --- a/solidity/test/updateValset.ts +++ b/solidity/test/updateValset.ts @@ -3,7 +3,15 @@ import {ethers} from "hardhat"; import {solidity} from "ethereum-waffle"; import {deployContracts, sortValidators} from "../test-utils"; -import {examplePowers, getSignerAddresses, makeCheckpoint, parseEvent, signHash, ZeroAddress} from "../test-utils/pure"; +import { + EmptyCosmosAddress, + EmptyDenom, + examplePowers, + getSignerAddresses, + makeCheckpoint, parseEvent, + signHash +} from "../test-utils/pure"; +import {BigNumber} from "ethers"; chai.use(solidity); const {expect} = chai; @@ -16,7 +24,7 @@ async function runTest(opts: { badValidatorSig?: boolean; zeroedValidatorSig?: boolean; notEnoughPower?: boolean; - badReward?: boolean; + emptyRewardRecipient?: boolean; notEnoughReward?: boolean; withReward?: boolean; notEnoughPowerNewSet?: boolean; @@ -25,15 +33,17 @@ async function runTest(opts: { }) { const signers = await ethers.getSigners(); const gravityId = ethers.utils.formatBytes32String("foo"); + const cosmosAddress = "cosmos1zkl8g9vd62x0ykvwq4mdcaehydvwc8ylh6panp" + const rewardDenom = "uatom" + const rewardAmount = 5000000 // This is the power distribution on the Cosmos hub as of 7/14/2020 let powers = examplePowers(); let validators = sortValidators(signers.slice(0, powers.length)); - const { - gravity, - testERC20, - } = await deployContracts(gravityId, validators, powers); + let rewardRecipient = EmptyCosmosAddress + + const { gravity } = await deployContracts(gravityId, validators, powers); let newPowers = examplePowers(); newPowers[0] -= 3; @@ -43,8 +53,7 @@ async function runTest(opts: { // arbitrarily set a duplicate validator if (opts.duplicateValidator) { - let firstValidator = newValidators[0]; - newValidators[22] = firstValidator; + newValidators[22] = newValidators[0]; } if (opts.malformedNewValset) { @@ -73,7 +82,7 @@ async function runTest(opts: { powers, valsetNonce: currentValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } let newValset = { @@ -81,38 +90,18 @@ async function runTest(opts: { powers: newPowers, valsetNonce: newValsetNonce, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } - let ERC20contract; - if (opts.badReward) { - // some amount of a reward, in a random token that's not in the bridge - // should panic because the token doesn't exist - newValset.rewardAmount = 5000000; - newValset.rewardToken = "0x8bcd7D3532CB626A7138962Bdb859737e5B6d4a7"; + if (opts.emptyRewardRecipient) { + // the reward is present but the recipient is empty + newValset.rewardAmount = rewardAmount; + newValset.rewardDenom = rewardDenom; } else if (opts.withReward) { - // deploy a ERC20 representing a Cosmos asset, as this is the common - // case for validator set rewards - const eventArgs = await parseEvent(gravity, gravity.deployERC20('uatom', 'Atom', 'ATOM', 6), 1) - newValset.rewardToken = eventArgs._tokenContract + newValset.rewardDenom = rewardDenom // five atom, issued as an inflationary reward - newValset.rewardAmount = 5000000 - - // connect with the contract to check balances later - ERC20contract = new ethers.Contract(eventArgs._tokenContract, [ - "function balanceOf(address account) view returns (uint256 balance)" - ], gravity.provider); - - } else if (opts.notEnoughReward) { - // send in 1000 tokens, then have a reward of five million - await testERC20.functions.approve(gravity.address, 1000); - await gravity.functions.sendToCosmos( - testERC20.address, - ethers.utils.formatBytes32String("myCosmosAddress"), - 1000 - ); - newValset.rewardToken = testERC20.address - newValset.rewardAmount = 5000000 + newValset.rewardAmount = rewardAmount + rewardRecipient = cosmosAddress } const checkpoint = makeCheckpoint( @@ -120,7 +109,7 @@ async function runTest(opts: { newValset.powers, newValset.valsetNonce, newValset.rewardAmount, - newValset.rewardToken, + newValset.rewardDenom, gravityId ); @@ -159,20 +148,24 @@ async function runTest(opts: { powers.pop(); } - let valsetUpdateTx = await gravity.updateValset( + const valsetUpdateEventArgs = await parseEvent(gravity, gravity.updateValset( newValset, currentValset, - sigs - ); + sigs, + rewardRecipient + ), 0) // check that the relayer was paid if (opts.withReward) { - // panic if we failed to deploy the contract earlier - expect(ERC20contract) - if (ERC20contract) { - expect((await ERC20contract.functions.balanceOf(await valsetUpdateTx.from))[0].toBigInt()) - .to.equal(BigInt(5000000)); - } + expect(valsetUpdateEventArgs).to.deep.equal({ + _newValsetNonce: BigNumber.from(1), + _eventNonce: BigNumber.from(2), + _rewardAmount: BigNumber.from(rewardAmount), + _rewardDenom: rewardDenom, + _rewardRecipient: rewardRecipient, + _validators: await getSignerAddresses(newValidators), + _powers: newPowers.map(item => BigNumber.from(item)) + }) } return {gravity, checkpoint}; @@ -239,19 +232,13 @@ describe("updateValset tests", function () { ); }); - it("throws on bad reward ", async function () { - await expect(runTest({badReward: true})).to.be.revertedWith( - "Address: call to non-contract" - ); - }); - - it("throws on not enough reward ", async function () { - await expect(runTest({notEnoughReward: true})).to.be.revertedWith( - "transfer amount exceeds balance" + it("throws on empty reward recipient", async function () { + await expect(runTest({emptyRewardRecipient: true})).to.be.revertedWith( + "IncorrectRewardRecipient()" ); }); - it("pays reward correctly", async function () { + it("emits the event with the reward", async function () { let {gravity, checkpoint} = await runTest({withReward: true}); expect((await gravity.functions.state_lastValsetCheckpoint())[0]).to.equal(checkpoint); }); @@ -286,7 +273,7 @@ describe("updateValset Go test hash", function () { powers: powers, valsetNonce: 0, rewardAmount: 0, - rewardToken: ZeroAddress + rewardDenom: EmptyDenom } const checkpoint = makeCheckpoint( @@ -294,7 +281,7 @@ describe("updateValset Go test hash", function () { newValset.powers, newValset.valsetNonce, newValset.rewardAmount, - newValset.rewardToken, + newValset.rewardDenom, gravityId ); @@ -307,7 +294,7 @@ describe("updateValset Go test hash", function () { "address[]", // validators "uint256[]", // powers "uint256", // rewardAmount - "address" // rewardToken + "string" // rewardDenom ], [ gravityId, @@ -316,7 +303,7 @@ describe("updateValset Go test hash", function () { newValset.validators, newValset.powers, newValset.rewardAmount, - newValset.rewardToken, + newValset.rewardDenom, ] ); const valsetDigest = ethers.utils.keccak256(abiEncodedValset); @@ -331,7 +318,7 @@ describe("updateValset Go test hash", function () { "powers": powers, "valsetNonce": newValset.valsetNonce, "rewardAmount": newValset.rewardAmount, - "rewardToken": newValset.rewardToken + "rewardDenom": newValset.rewardDenom }) console.log("abiEncodedValset:", abiEncodedValset) console.log("valsetDigest:", valsetDigest) diff --git a/tests/build-container.sh b/tests/build-container.sh index 4f0dfeabc..0b46fe796 100755 --- a/tests/build-container.sh +++ b/tests/build-container.sh @@ -6,8 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DOCKERFOLDER=$DIR/dockerfile REPOFOLDER=$DIR/.. -#docker system prune -a -f - # setup for Mac M1 Compatibility PLATFORM_CMD="" CROSS_COMPILE="" @@ -22,6 +20,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then CROSS_COMPILE="x86_64-linux-musl-" TARGET="x86_64-unknown-linux-musl" # the linker is also set in `orchestrator/.cargo/config` + # Here is the installation instruction https://github.com/FiloSottile/homebrew-musl-cross fi # By default we want to do a clean build, but for faster development `USE_LOCAL_ARTIFACTS=1` can