Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(sequencer): set/update reward addr #510

Merged
merged 118 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
40fc8f7
why
danwt Aug 15, 2024
8d4a062
docstrings
danwt Aug 15, 2024
5aae304
ready to implement
danwt Aug 15, 2024
bb9d7f2
ready to implement the txs
danwt Aug 15, 2024
5081075
cp
danwt Aug 15, 2024
2d28f43
regen proto
danwt Aug 15, 2024
51a27c8
init chain and genesis
danwt Aug 15, 2024
6eadfc8
need to check diff
danwt Aug 15, 2024
d86caf2
cp what I was doing - need to change task
danwt Aug 15, 2024
3083f76
got some notes, ready to impl
danwt Aug 15, 2024
822641d
ready to change proto
danwt Aug 15, 2024
f9ed20f
impl proto
danwt Aug 15, 2024
44da836
builds proto
danwt Aug 15, 2024
4c1f213
pre generate missing methods
danwt Aug 15, 2024
911c964
ready to try create
danwt Aug 15, 2024
7d71d7b
gonna write the verify method
danwt Aug 15, 2024
53ac51e
define payload to sign
danwt Aug 15, 2024
425873f
need to do sig verif
danwt Aug 15, 2024
212fd67
impl the check sig func
danwt Aug 15, 2024
a76abd4
time to test!
danwt Aug 15, 2024
8927472
delete a note
danwt Aug 15, 2024
3704231
gonna ut
danwt Aug 15, 2024
30e4e8a
gonna write a test
danwt Aug 15, 2024
b083960
gonna refac a bit
danwt Aug 15, 2024
824cc42
need to impl cons addr conversion
danwt Aug 15, 2024
bd5c90c
using validator as a basis of commands
danwt Aug 15, 2024
a2d13a9
pre del GetSdkPubKey MustGetConsAddr
danwt Aug 15, 2024
1afeddd
ready to test
danwt Aug 15, 2024
7d7e75e
lfg
danwt Aug 16, 2024
4acdad3
doc
danwt Aug 16, 2024
5ec4157
readability
danwt Aug 16, 2024
fab9262
doc
danwt Aug 16, 2024
a76b925
todo
danwt Aug 16, 2024
f02e3ca
todos
danwt Aug 16, 2024
db85002
need to get this thing tested
danwt Aug 16, 2024
f69ed5b
I should write the message create creator
danwt Aug 16, 2024
fab2e9e
lets check the signer on thecli
danwt Aug 16, 2024
6b6183a
impl some more, but need to finish
danwt Aug 16, 2024
19dde65
pre small refac
danwt Aug 16, 2024
9ecf4bc
now need to pass some kind of keyring to signer
danwt Aug 16, 2024
f332cbc
how to convert to *types.any
danwt Aug 16, 2024
2624838
how to get the private key?
danwt Aug 16, 2024
ba93ed5
write more of a test
danwt Aug 16, 2024
69df339
need to investigate keyring api
danwt Aug 16, 2024
0ab2746
keyring
danwt Aug 16, 2024
fa5c8d2
now just need to pass keyring
danwt Aug 16, 2024
544fcb5
can we integrate keyring?
danwt Aug 16, 2024
0567314
lets actually try the keyring I suppose
danwt Aug 16, 2024
71da846
need to fix tests
danwt Aug 16, 2024
dba8b79
trying to fix test, gonna simplify
danwt Aug 16, 2024
df40c50
does it work?
danwt Aug 16, 2024
a4de018
finish implementing happy path test
danwt Aug 16, 2024
ac1f5d4
adds todo for monday
danwt Aug 16, 2024
386eb1b
cp
danwt Aug 19, 2024
3e7d805
testing locally
danwt Aug 19, 2024
0807558
patch cli
danwt Aug 19, 2024
984815a
validate key type
danwt Aug 19, 2024
12817f9
why u do dis
danwt Aug 19, 2024
5c3fcf0
gonna try naive way
danwt Aug 19, 2024
241a17a
try to add key import
danwt Aug 19, 2024
d3d1d98
ready to rebuild and test
danwt Aug 19, 2024
704f36a
the test passes
danwt Aug 19, 2024
57ea16c
impl
danwt Aug 19, 2024
96ab28f
gonna cleanup the tx
danwt Aug 19, 2024
5e17676
ready for a full e2e test
danwt Aug 19, 2024
c06425b
pre test
danwt Aug 19, 2024
0a724a4
rebuild with cons addr
danwt Aug 19, 2024
220ea65
build
danwt Aug 19, 2024
1f885c9
why
danwt Aug 19, 2024
b76fda7
weird
danwt Aug 19, 2024
eb09b99
gonna switch to Michaels
danwt Aug 19, 2024
e619f94
try to impl unpack
danwt Aug 19, 2024
8a352ea
maybe it works
danwt Aug 19, 2024
9b2383d
adds events
danwt Aug 19, 2024
de848ed
the genesis dont work on main anyway ree
danwt Aug 19, 2024
02941d1
refactor cli
danwt Aug 19, 2024
cb55583
need to do an e2e test
danwt Aug 19, 2024
3106c3f
slim
danwt Aug 19, 2024
8b8c990
pre fix tests
danwt Aug 20, 2024
bc956d4
use address utils
danwt Aug 20, 2024
fe2f5d8
try to fix test
danwt Aug 20, 2024
7b06828
fix the non genesis tests
danwt Aug 20, 2024
804696e
fix linter
danwt Aug 20, 2024
af96199
linter
danwt Aug 20, 2024
a77e174
pre del unused util checkSigAccNumber
danwt Aug 20, 2024
92d3e57
del sig.go
danwt Aug 20, 2024
8407d31
define genesis protos
danwt Aug 20, 2024
2bdce13
regen proto
danwt Aug 20, 2024
3d4ade4
better docs
danwt Aug 20, 2024
c3a54e7
regen tx
danwt Aug 20, 2024
5794c18
regen genesis proto without te update
danwt Aug 20, 2024
71d03ff
ready to impl export import
danwt Aug 20, 2024
9fb38d0
impl export
danwt Aug 20, 2024
2c8d8a4
impl genesis
danwt Aug 20, 2024
3184dbf
need to test genesis
danwt Aug 20, 2024
e751bc1
cannot clone any
danwt Aug 20, 2024
736702a
return a new one instead of clone
danwt Aug 20, 2024
7732a2e
finish genesis test
danwt Aug 20, 2024
fc0c63d
all tests pass
danwt Aug 20, 2024
287e4da
tested
danwt Aug 20, 2024
0ef32c9
gonna change proto use operator
danwt Aug 20, 2024
6abd0e5
change to make signer the operator
danwt Aug 20, 2024
a4de769
worked on create
danwt Aug 20, 2024
080f77c
something aint right
danwt Aug 20, 2024
181f506
tests pass again
danwt Aug 20, 2024
88363d1
try to fix cli
danwt Aug 20, 2024
fd2b797
ready for another e2e test
danwt Aug 20, 2024
b77b39d
ready for another e2e test
danwt Aug 20, 2024
1b0cef6
fix cli
danwt Aug 20, 2024
3f903f5
small readability tweaks
danwt Aug 20, 2024
2fa0c29
improved unit test
danwt Aug 20, 2024
4568189
doc
danwt Aug 20, 2024
3a8040b
enhance docstring
danwt Aug 23, 2024
9ecc0b1
refactored sig verif
danwt Aug 23, 2024
4a775de
use default reward addr in cli
danwt Aug 23, 2024
7a943ac
remove operator
danwt Aug 23, 2024
a76d11e
simplify
danwt Aug 23, 2024
89526c5
double check security
danwt Aug 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,5 @@ replace (
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.28
)

replace github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.0-dymension-v1.1.2
9 changes: 2 additions & 7 deletions proto/sequencers/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ package rollapp.sequencers.types;
import "gogoproto/gogo.proto";
import "sequencers/params.proto";

// this line is used by starport scaffolding # genesis/proto/import

option go_package = "github.com/dymensionxyz/dymension-rdk/x/sequencers/types";

// GenesisState defines the test module's genesis state.
// GenesisState defines the module's genesis state.
message GenesisState {
Params params = 1 [ (gogoproto.nullable) = false ];

// genesis_operator_address defines the genesis operator address of the
// sequencer.
string genesis_operator_address = 2;
reserved 2;
}
2 changes: 0 additions & 2 deletions proto/sequencers/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import "google/api/annotations.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "sequencers/params.proto";

// this line is used by starport scaffolding # 1

option go_package = "github.com/dymensionxyz/dymension-rdk/x/sequencers/types";

Expand Down Expand Up @@ -93,4 +92,3 @@ message QueryParamsResponse {
Params params = 1 [ (gogoproto.nullable) = false ];
}

// this line is used by starport scaffolding # 3
73 changes: 73 additions & 0 deletions proto/sequencers/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
syntax = "proto3";
package rollapp.sequencers.types;

import "gogoproto/gogo.proto";
import "cosmos/staking/v1beta1/staking.proto";
//import "types/tendermint/crypto/keys.proto";
import "cosmos/msg/v1/msg.proto";
import "google/protobuf/any.proto";

// TODO: use sequencer object?
// Sequencer - only the
// cosmos.staking.v1beta1.Validator sequencer = 1
// [ (gogoproto.nullable) = false ];

option go_package = "github.com/dymensionxyz/dymension-rdk/x/sequencers/types";

// Msg defines the Msg service.
service Msg {
rpc CreateSequencer(MsgCreateSequencer) returns (MsgCreateSequencerResponse);
rpc UpdateSequencer(MsgUpdateSequencer) returns (MsgUpdateSequencerResponse);
}

// PayloadToSign is marshalled and signed
message PayloadToSign {
// PayloadApp is application specific signed data
danwt marked this conversation as resolved.
Show resolved Hide resolved
bytes payload_app = 1;
// ChainId will be verified against processing chain
string chain_id = 2;
// AccountNumber is the auth account keeper account number, it is verified against the message creator
uint64 account_number = 3;
}

message KeyAndSig {
google.protobuf.Any pub_key = 1;
// tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false];
bytes signature = 2; // signature for some <payload, chain id, account number, account addr, sequence num> // TODO: not seq num
}

message CreateSequencerPayload {
string operator_addr = 1; // TODO: document
}

message MsgCreateSequencer {
option (cosmos.msg.v1.signer) = "creator";
// Creator is the bech32-encoded address of the actor sending the update
string creator = 1;
// KeyAndSig has the cons key of the sequencer, as well as a sig over the payload and some replay protection metadata
KeyAndSig key_and_sig = 2;
// Payload - signature is in key and sig
CreateSequencerPayload payload = 3;
}

message MsgCreateSequencerResponse {

}

message UpdateSequencerPayload {
string reward_addr = 1;
}

message MsgUpdateSequencer {
option (cosmos.msg.v1.signer) = "creator";
// Creator is the bech32-encoded address of the actor sending the update
string creator = 1;
// KeyAndSig has the cons key of the sequencer, as well as a sig over the payload and some replay protection metadata
KeyAndSig key_and_sig = 2;
mtsitrin marked this conversation as resolved.
Show resolved Hide resolved
// Payload - signature is in key and sig
UpdateSequencerPayload payload = 3;
}

message MsgUpdateSequencerResponse {

}
2 changes: 1 addition & 1 deletion testutil/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.Res
}

// Passing the dymint sequencers to the sequencer module from RequestInitChain
app.SequencersKeeper.SetDymintSequencers(ctx, req.Validators)
app.SequencersKeeper.SetDymintValidatorUpdates(ctx, req.Validators)

app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap())
res := app.mm.InitGenesis(ctx, app.appCodec, genesisState)
Expand Down
42 changes: 17 additions & 25 deletions x/dist/keeper/allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,27 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, blockProposer sdk.ConsAddress) {

/* ---------------------------- Pay the proposer ---------------------------- */
// calculate and pay proposer reward
proposer, found := k.seqKeeper.GetSequencerByConsAddr(ctx, blockProposer)
addr, found := k.seqKeeper.GetRewardAddrByConsAddr(ctx, blockProposer)
if !found {
logger.Error("failed to find the validator for this block. reward not allocated")
logger.Error("Finding the validator for this block. Reward not allocated.")
} else {
proposerReward := feesCollected.MulDecTruncate(k.GetBaseProposerReward(ctx))
proposerCoins, proposerRemainder := proposerReward.TruncateDecimal()

err := k.AllocateTokensToSequencer(ctx, proposer, proposerCoins)
if err != nil {
logger.Error("failed to reward proposer", "error", err, "proposer", proposer.GetOperator())
} else {
remainingFees = feesCollected.Sub(proposerReward).Add(proposerRemainder...)

// update outstanding rewards
ctx.EventManager().EmitEvent(
sdk.NewEvent(
disttypes.EventTypeDistSequencerRewards,
sdk.NewAttribute(sdk.AttributeKeyAmount, proposerCoins.String()),
sdk.NewAttribute(disttypes.AttributeKeySequencer, proposer.GetOperator().String()),
),
)
if !proposerCoins.IsZero() {
mtsitrin marked this conversation as resolved.
Show resolved Hide resolved
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, proposerCoins)
if err != nil {
logger.Error("Send rewards to proposer.", "err", err, "proposer reward addr", addr)
} else {
remainingFees = feesCollected.Sub(proposerReward).Add(proposerRemainder...)
// update outstanding rewards
ctx.EventManager().EmitEvent(
sdk.NewEvent(
disttypes.EventTypeDistSequencerRewards,
sdk.NewAttribute(sdk.AttributeKeyAmount, proposerCoins.String()),
sdk.NewAttribute(disttypes.AttributeKeyRewardee, addr.String()),
),
)
}
}
}

Expand All @@ -75,11 +75,3 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, blockProposer sdk.ConsAddress) {
feePool.CommunityPool = feePool.CommunityPool.Add(remainingFees...)
k.SetFeePool(ctx, feePool)
}

func (k Keeper) AllocateTokensToSequencer(ctx sdk.Context, val stakingtypes.ValidatorI, tokens sdk.Coins) error {
if tokens.IsZero() {
return nil
}
accAddr := sdk.AccAddress(val.GetOperator())
return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accAddr, tokens)
}
4 changes: 2 additions & 2 deletions x/dist/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var (
_ module.AppModuleSimulation = AppModule{}
)

// AppModule embeds the Cosmos SDK's x/distribution AppModuleBasic.
// AppModuleBasic embeds the Cosmos SDK's x/distribution AppModuleBasic.
type AppModuleBasic struct {
distribution.AppModuleBasic
}
Expand Down Expand Up @@ -71,7 +71,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
func (am AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
defGenesis := types.DefaultGenesisState()

// by default, all rewards goes to the governers
// by default, all rewards goes to the governors
defGenesis.Params.CommunityTax = sdk.ZeroDec()
defGenesis.Params.BaseProposerReward = sdk.ZeroDec()
defGenesis.Params.BonusProposerReward = sdk.ZeroDec()
Expand Down
2 changes: 1 addition & 1 deletion x/dist/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package types
// Minting module event types.
const (
EventTypeDistSequencerRewards = "sequencer_rewards"
AttributeKeySequencer = "sequencer"
AttributeKeyRewardee = "rewardee"
)
6 changes: 3 additions & 3 deletions x/dist/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import (

// SequencerKeeper expected sequencer keeper (noalias)
type SequencerKeeper interface {
GetSequencerByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (stakingtypes.Validator, bool)
GetRewardAddrByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (sdk.AccAddress, bool)
}

// StakingKeeper expected staking keeper (noalias)
type StakingKeeper interface {
GetLastTotalPower(ctx sdk.Context) math.Int

// iterate through validators by operator address, execute func for each validator
// IterateValidators iterate through validators by operator address, execute func for each validator
IterateValidators(sdk.Context,
func(index int64, validator stakingtypes.ValidatorI) (stop bool))
// iterate through bonded validators by operator address, execute func for each validator
// IterateBondedValidatorsByPower iterate through bonded validators by operator address, execute func for each validator
IterateBondedValidatorsByPower(sdk.Context,
func(index int64, validator stakingtypes.ValidatorI) (stop bool))

Expand Down
2 changes: 1 addition & 1 deletion x/hub/types/genesis.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions x/sequencers/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/dymensionxyz/dymension-rdk/x/sequencers/types"

Check failure on line 10 in x/sequencers/client/cli/query.go

View workflow job for this annotation

GitHub Actions / golangci-lint

could not import github.com/dymensionxyz/dymension-rdk/x/sequencers/types (-: # github.com/dymensionxyz/dymension-rdk/x/sequencers/types
"github.com/spf13/cobra"
)

Expand All @@ -25,11 +25,8 @@
cmd.AddCommand(CmdQuerySequencers())
cmd.AddCommand(CmdQuerySequencer())

// TODO:
// cmd.AddCommand(CmdQueryHistoricalInfo())
// Add queries for specific sequencer (num of blocks, rewards, etc..)

// this line is used by starport scaffolding # 1
// TODO: historical info
// TODO: Add queries for specific sequencer (num of blocks, rewards, etc..)

return cmd
}
28 changes: 28 additions & 0 deletions x/sequencers/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sequencers

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/dymensionxyz/dymension-rdk/x/sequencers/keeper"
"github.com/dymensionxyz/dymension-rdk/x/sequencers/types"
)

// NewHandler returns a new msg handler.
func NewHandler(k keeper.Keeper) sdk.Handler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this file is redundant. in sdk 0.46 you simply need to simply register your msg server infront of your keeper and implement the corresponding methods in the keeper.

msgServer := keeper.NewMsgServerImpl(k)

return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())

switch msg := msg.(type) {
case *types.MsgCreateSequencer:
res, err := msgServer.CreateSequencer(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgUpdateSequencer:
res, err := msgServer.UpdateSequencer(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg)
}
}
}
53 changes: 10 additions & 43 deletions x/sequencers/keeper/dymint.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,21 @@
package keeper

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/dymensionxyz/gerr-cosmos/gerrc"
abci "github.com/tendermint/tendermint/abci/types"

"github.com/dymensionxyz/dymension-rdk/x/sequencers/types"

Check failure on line 9 in x/sequencers/keeper/dymint.go

View workflow job for this annotation

GitHub Actions / golangci-lint

could not import github.com/dymensionxyz/dymension-rdk/x/sequencers/types (-: # github.com/dymensionxyz/dymension-rdk/x/sequencers/types

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
tmcrypto "github.com/tendermint/tendermint/crypto/encoding"
)

// SetDymintSequencers sets the sequencers set by dymint on InitChain.
// As currently we're using the abci InitChain, there are 2 obstacles we need to face, unlike when creating a validator:
// 1. InitChain expected the validatorUpdate it gets in return to be the same as it sends
// 2. We need someway to set the operator address, which is the address that will be used for rewards for the sequencer.
// To overcome those obstacles, we do the following:
// 1. Upon InitChain, call SetDymintSequencers and create a dummy sequencer object with the consensus pubkey and power we get from the validatorUpdate.
// 2. Afterwards, upon initGenesis, we build a validator-like object where the operator address is the one we set in the genesis file and the
// consensus pubkey and power are the ones we set in the dummy sequencer object.
//
// At the end we delete the dummy sequencer object.
func (k Keeper) SetDymintSequencers(ctx sdk.Context, sequencers []abci.ValidatorUpdate) {
if len := len(sequencers); len != 1 {
switch len {
case 0:
panic(types.ErrNoSequencerOnInitChain)
default:
panic(types.ErrMultipleDymintSequencers)
}
}
seq := sequencers[0]

tmkey, err := tmcrypto.PubKeyFromProto(seq.PubKey)
if err != nil {
panic(err)
}
pubKey, err := cryptocodec.FromTmPubKeyInterface(tmkey)
if err != nil {
panic(err)
}

// On InitChain we only have the consesnsus pubkey, so we set the operator address to a dummy value
sequencer, err := types.NewSequencer(sdk.ValAddress(types.InitChainStubAddr), pubKey, seq.Power)
if err != nil {
panic(err)
}

k.SetSequencer(ctx, sequencer)
err = k.SetSequencerByConsAddr(ctx, sequencer)
if err != nil {
panic(err)
// SetDymintValidatorUpdates - ABCI expects the result of init genesis to return the same value as passed in InitChainer,
// so we save it to return later.
func (k Keeper) SetDymintValidatorUpdates(ctx sdk.Context, updates []abci.ValidatorUpdate) {
if len(updates) != 1 {
panic(errorsmod.Wrapf(gerrc.ErrOutOfRange, "expect 1 abci validator update: got: %d", len(updates)))
}
u := updates[0]
k.cdc.MustMarshal(&u)
ctx.KVStore(k.storeKey).Set(types.ValidatorUpdateKey, k.cdc.MustMarshal(&u))
}
Loading
Loading