├── buf.work.yaml ├── x ├── btcbridge │ ├── module │ │ ├── module_simulation.go │ │ ├── genesis.go │ │ ├── genesis_test.go │ │ ├── abci.go │ │ └── module.go │ ├── types │ │ ├── utxo.go │ │ ├── msg_update_params.go │ │ ├── asset.go │ │ ├── msg_submit_fee_rate.go │ │ ├── msg_submit_headers.go │ │ ├── msg_withdraw_to_bitcoin.go │ │ ├── msg_submit_signatures.go │ │ ├── merkle_proof.go │ │ ├── msg_update_trusted_non_btc_relayers.go │ │ ├── msg_update_trusted_fee_providers.go │ │ ├── msg_submit_withdraw_transaction.go │ │ ├── msg_submit_deposit_transaction.go │ │ ├── msg_complete_dkg.go │ │ ├── msg_transfer_vault.go │ │ ├── msg_initiate_dkg.go │ │ ├── msg_consolidate_vaults.go │ │ ├── runes_test.go │ │ ├── varint.go │ │ ├── tss.go │ │ ├── expected_keepers.go │ │ ├── codec.go │ │ ├── block_header.go │ │ ├── genesis.go │ │ ├── signature.go │ │ ├── errors.go │ │ ├── policy.go │ │ ├── keys.go │ │ └── runes.go │ ├── keeper │ │ ├── event.go │ │ ├── hooks.go │ │ ├── fee_rate.go │ │ ├── params.go │ │ └── consolidate.go │ ├── codec │ │ └── bech32_codec.go │ └── client │ │ └── cli │ │ └── tx.go └── incentive │ ├── module │ ├── module_simulation.go │ ├── genesis.go │ ├── genesis_test.go │ └── module.go │ ├── types │ ├── errors.go │ ├── genesis.go │ ├── msg_update_params.go │ ├── keys.go │ ├── codec.go │ ├── params.go │ └── expected_keepers.go │ ├── keeper │ ├── msg_server.go │ ├── queries.go │ ├── keeper.go │ ├── params.go │ └── reward.go │ └── client │ └── cli │ └── query.go ├── .gitignore ├── testutil ├── sample │ └── sample.go ├── nullify │ └── nullify.go ├── keeper │ ├── incentive.go │ └── btc_bridge.go └── network │ └── network.go ├── proto ├── side │ ├── incentive │ │ ├── genesis.proto │ │ ├── params.proto │ │ ├── incentive.proto │ │ ├── tx.proto │ │ └── query.proto │ └── btcbridge │ │ ├── genesis.proto │ │ ├── params.proto │ │ └── btcbridge.proto ├── buf.gen.swagger.yaml ├── buf.gen.sta.yaml ├── buf.gen.ts.yaml ├── buf.gen.gogo.yaml ├── buf.gen.pulsar.yaml ├── buf.yaml └── buf.lock ├── cmd └── sided │ ├── main.go │ └── cmd │ ├── config.go │ ├── root.go │ └── commands.go ├── tools └── tools.go ├── app ├── params │ └── encoding.go ├── wasm.go ├── genesis.go ├── config.go ├── test_support.go ├── encoding.go └── export.go ├── docs ├── docs.go └── template │ └── index.tpl ├── config.yml ├── README.md ├── .github └── workflows │ └── release.yml ├── third_node.sh ├── second_node.sh └── api └── side └── incentive ├── tx_grpc.pb.go └── query_grpc.pb.go /buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | directories: 3 | - proto 4 | -------------------------------------------------------------------------------- /x/btcbridge/module/module_simulation.go: -------------------------------------------------------------------------------- 1 | package btcbridge 2 | -------------------------------------------------------------------------------- /x/incentive/module/module_simulation.go: -------------------------------------------------------------------------------- 1 | package incentive 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vue/node_modules 2 | vue/dist 3 | build/ 4 | release/ 5 | .idea/ 6 | .vscode/ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /x/btcbridge/types/utxo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // UTXOIterator defines the interface of the iterator over the utxos 4 | type UTXOIterator interface { 5 | Valid() bool 6 | Next() 7 | Close() error 8 | 9 | GetUTXO() *UTXO 10 | GetMinimumUTXO() *UTXO 11 | } 12 | -------------------------------------------------------------------------------- /testutil/sample/sample.go: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | // AccAddress returns a sample account address 9 | func AccAddress() string { 10 | pk := ed25519.GenPrivKey().PubKey() 11 | addr := pk.Address() 12 | return sdk.AccAddress(addr).String() 13 | } 14 | -------------------------------------------------------------------------------- /proto/side/incentive/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.incentive; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "side/incentive/params.proto"; 6 | 7 | option go_package = "github.com/sideprotocol/side/x/incentive/types"; 8 | 9 | // GenesisState defines the incentive module's genesis state. 10 | message GenesisState { 11 | Params params = 1 [(gogoproto.nullable) = false]; 12 | } 13 | -------------------------------------------------------------------------------- /proto/buf.gen.swagger.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.gen.swagger.yaml 5 | # 6 | version: v1 7 | plugins: 8 | - name: openapiv2 9 | out: . 10 | opt: 11 | - logtostderr=true 12 | - openapi_naming_strategy=fqn 13 | - json_names_for_fields=false 14 | - generate_unbound_methods=true -------------------------------------------------------------------------------- /proto/buf.gen.sta.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.gen.sta.yaml 5 | # 6 | version: v1 7 | plugins: 8 | - name: openapiv2 9 | out: . 10 | opt: 11 | - logtostderr=true 12 | - openapi_naming_strategy=simple 13 | - ignore_comments=true 14 | - simple_operation_ids=false 15 | - json_names_for_fields=false 16 | -------------------------------------------------------------------------------- /cmd/sided/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "cosmossdk.io/log" 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/sideprotocol/side/app" 10 | "github.com/sideprotocol/side/cmd/sided/cmd" 11 | ) 12 | 13 | func main() { 14 | rootCmd := cmd.NewRootCmd() 15 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { 16 | log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) 17 | os.Exit(1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /x/incentive/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | errorsmod "cosmossdk.io/errors" 7 | ) 8 | 9 | // x/incentive module sentinel errors 10 | var ( 11 | ErrDepositIncentiveNotEnabled = errorsmod.Register(ModuleName, 1001, "incentive not enabled for deposit") 12 | ErrWithdrawIncentiveNotEnabled = errorsmod.Register(ModuleName, 1002, "incentive not enabled for withdrawal") 13 | ErrInvalidParams = errorsmod.Register(ModuleName, 1003, "invalid params") 14 | ) 15 | -------------------------------------------------------------------------------- /proto/buf.gen.ts.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.gen.ts.yaml 5 | # 6 | version: v1 7 | managed: 8 | enabled: true 9 | plugins: 10 | - plugin: buf.build/community/stephenh-ts-proto 11 | out: . 12 | opt: 13 | - logtostderr=true 14 | - allow_merge=true 15 | - json_names_for_fields=false 16 | - ts_proto_opt=snakeToCamel=true 17 | - ts_proto_opt=esModuleInterop=true 18 | - ts_proto_out=. 19 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/bufbuild/buf/cmd/buf" 7 | _ "github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar" 8 | _ "github.com/cosmos/gogoproto/protoc-gen-gocosmos" 9 | _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway" 10 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 11 | _ "golang.org/x/tools/cmd/goimports" 12 | _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" 13 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 14 | ) 15 | -------------------------------------------------------------------------------- /proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.gen.gogo.yaml 5 | # 6 | version: v1 7 | plugins: 8 | - name: gocosmos 9 | out: . 10 | opt: 11 | - plugins=grpc 12 | - Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types 13 | - Mcosmos/orm/v1/orm.proto=cosmossdk.io/orm 14 | - name: grpc-gateway 15 | out: . 16 | opt: 17 | - logtostderr=true 18 | - allow_colon_final_segments=true 19 | -------------------------------------------------------------------------------- /app/params/encoding.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | "github.com/cosmos/cosmos-sdk/codec/types" 7 | ) 8 | 9 | // EncodingConfig specifies the concrete encoding types to use for a given app. 10 | // This is provided for compatibility between protobuf and amino implementations. 11 | type EncodingConfig struct { 12 | InterfaceRegistry types.InterfaceRegistry 13 | Codec codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /x/btcbridge/keeper/event.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/sideprotocol/side/x/btcbridge/types" 7 | ) 8 | 9 | func (k Keeper) EmitEvent(ctx sdk.Context, sender string, attr ...sdk.Attribute) { 10 | headerAttr := []sdk.Attribute{ 11 | { 12 | Key: "sender", 13 | Value: sender, 14 | }, 15 | } 16 | 17 | headerAttr = append(headerAttr, attr...) 18 | ctx.EventManager().EmitEvent( 19 | sdk.NewEvent( 20 | types.ModuleName, 21 | // attr..., 22 | headerAttr..., 23 | ), 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_update_params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgUpdateParams{} 9 | 10 | // ValidateBasic performs basic MsgUpdateParams message validation. 11 | func (m *MsgUpdateParams) ValidateBasic() error { 12 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 13 | return errorsmod.Wrap(err, "invalid authority address") 14 | } 15 | 16 | if err := m.Params.Validate(); err != nil { 17 | return err 18 | } 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /x/incentive/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DefaultGenesis returns the default genesis state 4 | func DefaultGenesis() *GenesisState { 5 | return &GenesisState{ 6 | // this line is used by starport scaffolding # genesis/types/default 7 | Params: DefaultParams(), 8 | } 9 | } 10 | 11 | // Validate performs basic genesis state validation returning an error upon any 12 | // failure. 13 | func (gs GenesisState) Validate() error { 14 | // this line is used by starport scaffolding # genesis/types/validate 15 | 16 | // validate params 17 | return gs.Params.Validate() 18 | } 19 | -------------------------------------------------------------------------------- /x/incentive/types/msg_update_params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgUpdateParams{} 9 | 10 | // ValidateBasic performs basic MsgUpdateParams message validation. 11 | func (m *MsgUpdateParams) ValidateBasic() error { 12 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 13 | return errorsmod.Wrap(err, "invalid authority address") 14 | } 15 | 16 | if err := m.Params.Validate(); err != nil { 17 | return err 18 | } 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /proto/buf.gen.pulsar.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.gen.pulsar.yaml 5 | # 6 | version: v1 7 | managed: 8 | enabled: true 9 | go_package_prefix: 10 | default: cosmossdk.io/api 11 | except: 12 | - buf.build/googleapis/googleapis 13 | - buf.build/cosmos/gogo-proto 14 | - buf.build/cosmos/cosmos-proto 15 | override: 16 | plugins: 17 | - name: go-pulsar 18 | out: ./api 19 | opt: paths=source_relative 20 | - name: go-grpc 21 | out: ./api 22 | opt: paths=source_relative 23 | -------------------------------------------------------------------------------- /proto/side/btcbridge/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.btcbridge; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "side/btcbridge/params.proto"; 6 | import "side/btcbridge/btcbridge.proto"; 7 | 8 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types"; 9 | 10 | // GenesisState defines the btc bridge module's genesis state. 11 | message GenesisState { 12 | Params params = 1 [(gogoproto.nullable) = false]; 13 | // the chain tip of the bitcoin chain 14 | BlockHeader best_block_header = 2; 15 | repeated BlockHeader block_headers = 3; 16 | repeated UTXO utxos = 4; 17 | DKGRequest dkg_request= 5; 18 | } 19 | -------------------------------------------------------------------------------- /proto/side/incentive/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.incentive; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos/base/v1beta1/coin.proto"; 6 | 7 | option go_package = "github.com/sideprotocol/side/x/incentive/types"; 8 | 9 | // Params defines the parameters for the module. 10 | message Params { 11 | // Indicates if the incentive mechanism is enabled 12 | bool enabled = 1; 13 | // Reward per deposit tx via btc bridge 14 | cosmos.base.v1beta1.Coin reward_per_deposit = 2 [(gogoproto.nullable) = false]; 15 | // Reward per withdrawal tx via btc bridge 16 | cosmos.base.v1beta1.Coin reward_per_withdraw = 3 [(gogoproto.nullable) = false]; 17 | } 18 | -------------------------------------------------------------------------------- /x/btcbridge/types/asset.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // AssetTypeFromDenom returns the asset type according to the denom 9 | func AssetTypeFromDenom(denom string, p Params) AssetType { 10 | if denom == p.BtcVoucherDenom { 11 | return AssetType_ASSET_TYPE_BTC 12 | } 13 | 14 | if strings.HasPrefix(denom, fmt.Sprintf("%s/", RunesProtocolName)) { 15 | return AssetType_ASSET_TYPE_RUNES 16 | } 17 | 18 | return AssetType_ASSET_TYPE_UNSPECIFIED 19 | } 20 | 21 | // SupportedAssetTypes returns the currently supported asset types 22 | func SupportedAssetTypes() []AssetType { 23 | return []AssetType{AssetType_ASSET_TYPE_BTC, AssetType_ASSET_TYPE_RUNES} 24 | } 25 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_submit_fee_rate.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgSubmitFeeRate{} 9 | 10 | func NewMsgSubmitFeeRate( 11 | sender string, 12 | feeRate int64, 13 | ) *MsgSubmitFeeRate { 14 | return &MsgSubmitFeeRate{ 15 | Sender: sender, 16 | FeeRate: feeRate, 17 | } 18 | } 19 | 20 | func (msg *MsgSubmitFeeRate) ValidateBasic() error { 21 | _, err := sdk.AccAddressFromBech32(msg.Sender) 22 | if err != nil { 23 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 24 | } 25 | 26 | if msg.FeeRate <= 0 { 27 | return ErrInvalidFeeRate 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /x/incentive/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | // ModuleName defines the module name 5 | ModuleName = "incentive" 6 | 7 | // StoreKey defines the primary module store key 8 | StoreKey = ModuleName 9 | 10 | // RouterKey defines the module's message routing key 11 | RouterKey = ModuleName 12 | 13 | // MemStoreKey defines the in-memory store key 14 | MemStoreKey = "mem_incentive" 15 | ) 16 | 17 | var ( 18 | ParamsStoreKey = []byte{0x1} 19 | 20 | RewardStatsKey = []byte{0x11} // key for total reward statistics 21 | RewardsKeyPrefix = []byte{0x12} // prefix for each key to the rewards 22 | ) 23 | 24 | func RewardsKey(address string) []byte { 25 | return append(RewardsKeyPrefix, []byte(address)...) 26 | } 27 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. You can edit 2 | # the file content but do not change the file name or path. 3 | # 4 | # buf.yaml 5 | # 6 | version: v1 7 | deps: 8 | - buf.build/protocolbuffers/wellknowntypes 9 | - buf.build/cosmos/cosmos-sdk 10 | - buf.build/cosmos/cosmos-proto 11 | - buf.build/cosmos/gogo-proto 12 | - buf.build/googleapis/googleapis 13 | - buf.build/cosmos/ics23 14 | breaking: 15 | use: 16 | - FILE 17 | lint: 18 | use: 19 | - DEFAULT 20 | - COMMENTS 21 | - FILE_LOWER_SNAKE_CASE 22 | except: 23 | - UNARY_RPC 24 | - COMMENT_FIELD 25 | - SERVICE_SUFFIX 26 | - PACKAGE_VERSION_SUFFIX 27 | - RPC_REQUEST_STANDARD_NAME 28 | ignore: 29 | - tendermint 30 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_submit_headers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgSubmitBlockHeaders{} 9 | 10 | func NewMsgSubmitBlockHeaders( 11 | sender string, 12 | headers []*BlockHeader, 13 | ) *MsgSubmitBlockHeaders { 14 | return &MsgSubmitBlockHeaders{ 15 | Sender: sender, 16 | BlockHeaders: headers, 17 | } 18 | } 19 | 20 | func (msg *MsgSubmitBlockHeaders) ValidateBasic() error { 21 | _, err := sdk.AccAddressFromBech32(msg.Sender) 22 | if err != nil { 23 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 24 | } 25 | 26 | if err := BlockHeaders(msg.BlockHeaders).Validate(); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /app/wasm.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" 5 | ) 6 | 7 | const ( 8 | // DefaultInstanceCost is initially set the same as in wasmd 9 | DefaultInstanceCost uint64 = 60_000 10 | // DefaultCompileCost set to a large number for testing 11 | DefaultCompileCost uint64 = 100 12 | ) 13 | 14 | // MunGasRegisterConfig is defaults plus a custom compile amount 15 | func GasRegisterConfig() wasmtypes.WasmGasRegisterConfig { 16 | gasConfig := wasmtypes.DefaultGasRegisterConfig() 17 | gasConfig.InstanceCost = DefaultInstanceCost 18 | gasConfig.CompileCost = DefaultCompileCost 19 | 20 | return gasConfig 21 | } 22 | 23 | func NewSideWasmGasRegister() wasmtypes.WasmGasRegister { 24 | return wasmtypes.NewWasmGasRegister(GasRegisterConfig()) 25 | } 26 | -------------------------------------------------------------------------------- /proto/side/incentive/incentive.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.incentive; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos/base/v1beta1/coin.proto"; 6 | 7 | option go_package = "github.com/sideprotocol/side/x/incentive/types"; 8 | 9 | // Rewards 10 | message Rewards { 11 | string address = 1; 12 | uint64 deposit_count = 2; 13 | uint64 withdraw_count = 3; 14 | cosmos.base.v1beta1.Coin deposit_reward = 4 [(gogoproto.nullable) = false]; 15 | cosmos.base.v1beta1.Coin withdraw_reward = 5 [(gogoproto.nullable) = false]; 16 | cosmos.base.v1beta1.Coin total_amount = 6 [(gogoproto.nullable) = false]; 17 | } 18 | 19 | // Reward Statistics 20 | message RewardStats { 21 | uint64 address_count = 1; 22 | uint64 tx_count = 2; 23 | cosmos.base.v1beta1.Coin total_reward_amount = 3 [(gogoproto.nullable) = false]; 24 | } 25 | -------------------------------------------------------------------------------- /app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | ) 8 | 9 | // The genesis state of the blockchain is represented here as a map of raw json 10 | // messages key'd by a identifier string. 11 | // The identifier is used to determine which module genesis information belongs 12 | // to so it may be appropriately routed during init chain. 13 | // Within this application default genesis information is retrieved from 14 | // the ModuleBasicManager which populates json from each BasicModule 15 | // object provided to it during init. 16 | type GenesisState map[string]json.RawMessage 17 | 18 | // NewDefaultGenesisState generates the default state for the application. 19 | func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { 20 | return ModuleBasics.DefaultGenesis(cdc) 21 | } 22 | -------------------------------------------------------------------------------- /x/incentive/module/genesis.go: -------------------------------------------------------------------------------- 1 | package incentive 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/sideprotocol/side/x/incentive/keeper" 7 | "github.com/sideprotocol/side/x/incentive/types" 8 | ) 9 | 10 | // InitGenesis initializes the module's state from a provided genesis state. 11 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { 12 | // this line is used by starport scaffolding # genesis/module/init 13 | k.SetParams(ctx, genState.Params) 14 | } 15 | 16 | // ExportGenesis returns the module's exported genesis 17 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 18 | genesis := types.DefaultGenesis() 19 | genesis.Params = k.GetParams(ctx) 20 | 21 | // this line is used by starport scaffolding # genesis/module/export 22 | 23 | return genesis 24 | } 25 | -------------------------------------------------------------------------------- /x/incentive/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/msgservice" 8 | ) 9 | 10 | func RegisterCodec(cdc *codec.LegacyAmino) { 11 | cdc.RegisterConcrete(&MsgUpdateParams{}, "incentive/MsgUpdateParams", nil) 12 | // this line is used by starport scaffolding # 2 13 | } 14 | 15 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { 16 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateParams{}) 17 | // this line is used by starport scaffolding # 3 18 | 19 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 20 | } 21 | 22 | var ( 23 | Amino = codec.NewLegacyAmino() 24 | ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) 25 | ) 26 | -------------------------------------------------------------------------------- /app/config.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | func init() { 10 | // Set prefixes 11 | accountPubKeyPrefix := AccountAddressPrefix + "pub" 12 | validatorAddressPrefix := AccountAddressPrefix + "valoper" 13 | validatorPubKeyPrefix := AccountAddressPrefix + "valoperpub" 14 | consNodeAddressPrefix := AccountAddressPrefix + "valcons" 15 | consNodePubKeyPrefix := AccountAddressPrefix + "valconspub" 16 | 17 | // Set and seal config 18 | config := sdk.GetConfig() 19 | config.SetBech32PrefixForAccount(AccountAddressPrefix, accountPubKeyPrefix) 20 | config.SetBech32PrefixForValidator(validatorAddressPrefix, validatorPubKeyPrefix) 21 | config.SetBech32PrefixForConsensusNode(consNodeAddressPrefix, consNodePubKeyPrefix) 22 | config.SetBtcChainCfg(&chaincfg.MainNetParams) 23 | config.Seal() 24 | } 25 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_withdraw_to_bitcoin.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgWithdrawToBitcoin{} 9 | 10 | func NewMsgWithdrawToBitcoin( 11 | sender string, 12 | amount string, 13 | ) *MsgWithdrawToBitcoin { 14 | return &MsgWithdrawToBitcoin{ 15 | Sender: sender, 16 | Amount: amount, 17 | } 18 | } 19 | 20 | func (msg *MsgWithdrawToBitcoin) ValidateBasic() error { 21 | _, err := sdk.AccAddressFromBech32(msg.Sender) 22 | if err != nil { 23 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 24 | } 25 | 26 | if !IsValidBtcAddress(msg.Sender) { 27 | return ErrInvalidBtcAddress 28 | } 29 | 30 | _, err = sdk.ParseCoinNormalized(msg.Amount) 31 | if err != nil { 32 | return errorsmod.Wrapf(err, "invalid withdrawal amount") 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from Ignite. 2 | # DO NOT EDIT 3 | # 4 | # buf.lock 5 | # 6 | version: v1 7 | deps: 8 | - remote: buf.build 9 | owner: cosmos 10 | repository: cosmos-proto 11 | commit: 1935555c206d4afb9e94615dfd0fad31 12 | - remote: buf.build 13 | owner: cosmos 14 | repository: cosmos-sdk 15 | commit: 954f7b05f38440fc8250134b15adec47 16 | - remote: buf.build 17 | owner: cosmos 18 | repository: gogo-proto 19 | commit: 34d970b699f84aa382f3c29773a60836 20 | - remote: buf.build 21 | owner: cosmos 22 | repository: ics23 23 | commit: 3c44d8daa8b44059ac744cd17d4a49d7 24 | - remote: buf.build 25 | owner: googleapis 26 | repository: googleapis 27 | commit: 75b4300737fb4efca0831636be94e517 28 | - remote: buf.build 29 | owner: protocolbuffers 30 | repository: wellknowntypes 31 | commit: 44e83bc050a4497fa7b36b34d95ca156 32 | -------------------------------------------------------------------------------- /x/btcbridge/keeper/hooks.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | // AfterDeposit performs the extended logic after deposit 8 | func (k Keeper) AfterDeposit(ctx sdk.Context, addr string) error { 9 | // distribute deposit reward 10 | if k.incentiveKeeper.DepositIncentiveEnabled(ctx) { 11 | _ = k.incentiveKeeper.DistributeDepositReward(ctx, addr) 12 | } 13 | 14 | return nil 15 | } 16 | 17 | // AfterWithdraw performs the extended logic after withdrawal 18 | func (k Keeper) AfterWithdraw(ctx sdk.Context, txHash string) error { 19 | // distribute rewards for all withdrawals 20 | if k.incentiveKeeper.WithdrawIncentiveEnabled(ctx) { 21 | withdrawRequests := k.GetWithdrawRequestsByTxHash(ctx, txHash) 22 | for _, req := range withdrawRequests { 23 | _ = k.incentiveKeeper.DistributeWithdrawReward(ctx, req.Address) 24 | } 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_submit_signatures.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgSubmitSignatures{} 9 | 10 | func NewMsgSubmitSignatures( 11 | sender string, 12 | txid string, 13 | pbst string, 14 | ) *MsgSubmitSignatures { 15 | return &MsgSubmitSignatures{ 16 | Sender: sender, 17 | Txid: txid, 18 | Psbt: pbst, 19 | } 20 | } 21 | 22 | func (msg *MsgSubmitSignatures) ValidateBasic() error { 23 | _, err := sdk.AccAddressFromBech32(msg.Sender) 24 | if err != nil { 25 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 26 | } 27 | 28 | if len(msg.Txid) == 0 { 29 | return errorsmod.Wrap(ErrInvalidSignatures, "txid cannot be empty") 30 | } 31 | 32 | if len(msg.Psbt) == 0 { 33 | return errorsmod.Wrap(ErrInvalidSignatures, "psbt cannot be empty") 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | package docs 2 | 3 | import ( 4 | "embed" 5 | httptemplate "html/template" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | const ( 12 | apiFile = "/static/openapi.yml" 13 | indexFile = "template/index.tpl" 14 | ) 15 | 16 | //go:embed static 17 | var Static embed.FS 18 | 19 | //go:embed template 20 | var template embed.FS 21 | 22 | func RegisterOpenAPIService(appName string, rtr *mux.Router) { 23 | rtr.Handle(apiFile, http.FileServer(http.FS(Static))) 24 | rtr.HandleFunc("/", handler(appName)) 25 | } 26 | 27 | // handler returns an http handler that servers OpenAPI console for an OpenAPI spec at specURL. 28 | func handler(title string) http.HandlerFunc { 29 | t, _ := httptemplate.ParseFS(template, indexFile) 30 | 31 | return func(w http.ResponseWriter, req *http.Request) { 32 | t.Execute(w, struct { 33 | Title string 34 | URL string 35 | }{ 36 | title, 37 | apiFile, 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /x/btcbridge/types/merkle_proof.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "github.com/btcsuite/btcd/blockchain" 7 | "github.com/btcsuite/btcd/chaincfg/chainhash" 8 | ) 9 | 10 | // VerifyMerkleProof verifies the merkle proof 11 | func VerifyMerkleProof(proofs []string, txHash, root *chainhash.Hash) bool { 12 | current := txHash 13 | for _, proof := range proofs { 14 | 15 | bytes, err := base64.StdEncoding.DecodeString(proof) 16 | if err != nil { 17 | return false 18 | } 19 | position := bytes[0] 20 | p := current 21 | if len(bytes) > 1 { 22 | p, err = chainhash.NewHash(bytes[1:]) 23 | if err != nil { 24 | return false 25 | } 26 | } 27 | 28 | var temp chainhash.Hash 29 | if position == 0 { 30 | temp = blockchain.HashMerkleBranches(current, p) 31 | } else { 32 | temp = blockchain.HashMerkleBranches(p, current) 33 | } 34 | current = &temp 35 | } 36 | 37 | return current.IsEqual(root) 38 | } 39 | -------------------------------------------------------------------------------- /docs/template/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .Title }} 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 24 | 25 | 26 | Footer 27 | © 2022 GitHub, Inc. 28 | Footer navigation 29 | -------------------------------------------------------------------------------- /x/incentive/module/genesis_test.go: -------------------------------------------------------------------------------- 1 | package incentive_test 2 | 3 | // Path: x/incentive/genesis_test.go 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | keepertest "github.com/sideprotocol/side/testutil/keeper" 11 | "github.com/sideprotocol/side/testutil/nullify" 12 | incentive "github.com/sideprotocol/side/x/incentive/module" 13 | "github.com/sideprotocol/side/x/incentive/types" 14 | ) 15 | 16 | func TestGenesis(t *testing.T) { 17 | mnemonic := "sunny bamboo garlic fold reopen exile letter addict forest vessel square lunar shell number deliver cruise calm artist fire just kangaroo suit wheel extend" 18 | println(mnemonic) 19 | 20 | genesisState := types.DefaultGenesis() 21 | 22 | k, ctx := keepertest.IncentiveKeeper(t) 23 | incentive.InitGenesis(ctx, k, *genesisState) 24 | got := incentive.ExportGenesis(ctx, k) 25 | require.NotNil(t, got) 26 | 27 | nullify.Fill(&genesisState) 28 | nullify.Fill(got) 29 | 30 | // this line is used by starport scaffolding # genesis/test/assert 31 | } 32 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_update_trusted_non_btc_relayers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgUpdateTrustedNonBtcRelayers{} 9 | 10 | func NewMsgUpdateTrustedNonBtcRelayers( 11 | sender string, 12 | relayers []string, 13 | ) *MsgUpdateTrustedNonBtcRelayers { 14 | return &MsgUpdateTrustedNonBtcRelayers{ 15 | Sender: sender, 16 | Relayers: relayers, 17 | } 18 | } 19 | 20 | func (msg *MsgUpdateTrustedNonBtcRelayers) ValidateBasic() error { 21 | _, err := sdk.AccAddressFromBech32(msg.Sender) 22 | if err != nil { 23 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 24 | } 25 | 26 | if len(msg.Relayers) == 0 { 27 | return errorsmod.Wrapf(ErrInvalidRelayers, "relayers can not be empty") 28 | } 29 | 30 | for _, relayer := range msg.Relayers { 31 | _, err := sdk.AccAddressFromBech32(relayer) 32 | if err != nil { 33 | return errorsmod.Wrapf(err, "invalid relayer address (%s)", err) 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_update_trusted_fee_providers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgUpdateTrustedFeeProviders{} 9 | 10 | func NewMsgUpdateTrustedFeeProviders( 11 | sender string, 12 | feeProviders []string, 13 | ) *MsgUpdateTrustedFeeProviders { 14 | return &MsgUpdateTrustedFeeProviders{ 15 | Sender: sender, 16 | FeeProviders: feeProviders, 17 | } 18 | } 19 | 20 | func (msg *MsgUpdateTrustedFeeProviders) ValidateBasic() error { 21 | _, err := sdk.AccAddressFromBech32(msg.Sender) 22 | if err != nil { 23 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 24 | } 25 | 26 | if len(msg.FeeProviders) == 0 { 27 | return errorsmod.Wrapf(ErrInvalidFeeProviders, "fee providers can not be empty") 28 | } 29 | 30 | for _, provider := range msg.FeeProviders { 31 | _, err := sdk.AccAddressFromBech32(provider) 32 | if err != nil { 33 | return errorsmod.Wrapf(err, "invalid fee provider address (%s)", err) 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /x/btcbridge/keeper/fee_rate.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/sideprotocol/side/x/btcbridge/types" 7 | ) 8 | 9 | // SetFeeRate sets the bitcoin network fee rate 10 | func (k Keeper) SetFeeRate(ctx sdk.Context, feeRate int64) { 11 | store := ctx.KVStore(k.storeKey) 12 | 13 | feeRateWithHeight := types.FeeRate{ 14 | Value: feeRate, 15 | Height: ctx.BlockHeight(), 16 | } 17 | 18 | store.Set(types.BtcFeeRateKey, k.cdc.MustMarshal(&feeRateWithHeight)) 19 | } 20 | 21 | // GetFeeRate gets the bitcoin network fee rate 22 | func (k Keeper) GetFeeRate(ctx sdk.Context) *types.FeeRate { 23 | store := ctx.KVStore(k.storeKey) 24 | 25 | var feeRate types.FeeRate 26 | bz := store.Get(types.BtcFeeRateKey) 27 | k.cdc.MustUnmarshal(bz, &feeRate) 28 | 29 | return &feeRate 30 | } 31 | 32 | // CheckFeeRate checks the given fee rate 33 | func (k Keeper) CheckFeeRate(ctx sdk.Context, feeRate *types.FeeRate) error { 34 | if feeRate.Value == 0 || ctx.BlockHeight()-feeRate.Height > k.GetParams(ctx).FeeRateValidityPeriod { 35 | return types.ErrInvalidFeeRate 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /x/incentive/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var ( 9 | // default reward per deposit tx via btc bridge 10 | DefaultRewardPerDeposit = sdk.NewInt64Coin("uside", 100000000) // 100SIDE 11 | 12 | // default reward per withdrawal tx via btc bridge 13 | DefaultRewardPerWithdraw = sdk.NewInt64Coin("uside", 100000000) // 100SIDE 14 | ) 15 | 16 | // NewParams creates a new Params instance 17 | func NewParams() Params { 18 | return Params{ 19 | Enabled: true, 20 | RewardPerDeposit: DefaultRewardPerDeposit, 21 | RewardPerWithdraw: DefaultRewardPerWithdraw, 22 | } 23 | } 24 | 25 | // DefaultParams returns a default set of parameters 26 | func DefaultParams() Params { 27 | return NewParams() 28 | } 29 | 30 | // Validate validates the set of params 31 | func (p Params) Validate() error { 32 | if !p.RewardPerDeposit.IsValid() { 33 | return errorsmod.Wrap(ErrInvalidParams, "invalid deposit reward") 34 | } 35 | 36 | if !p.RewardPerWithdraw.IsValid() { 37 | return errorsmod.Wrap(ErrInvalidParams, "invalid withdrawal reward") 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /x/incentive/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 9 | 10 | "github.com/sideprotocol/side/x/incentive/types" 11 | ) 12 | 13 | type msgServer struct { 14 | Keeper 15 | } 16 | 17 | // UpdateParams updates the module params. 18 | func (m msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { 19 | if m.authority != msg.Authority { 20 | return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", m.authority, msg.Authority) 21 | } 22 | 23 | if err := msg.ValidateBasic(); err != nil { 24 | return nil, err 25 | } 26 | 27 | ctx := sdk.UnwrapSDKContext(goCtx) 28 | m.SetParams(ctx, msg.Params) 29 | 30 | return &types.MsgUpdateParamsResponse{}, nil 31 | } 32 | 33 | // NewMsgServerImpl returns an implementation of the MsgServer interface 34 | // for the provided Keeper. 35 | func NewMsgServerImpl(keeper Keeper) types.MsgServer { 36 | return &msgServer{Keeper: keeper} 37 | } 38 | 39 | var _ types.MsgServer = msgServer{} 40 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_submit_withdraw_transaction.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgSubmitWithdrawTransaction{} 9 | 10 | func NewMsgSubmitWithdrawTransaction( 11 | sender string, 12 | blockhash string, 13 | transaction string, 14 | proof []string, 15 | ) *MsgSubmitWithdrawTransaction { 16 | return &MsgSubmitWithdrawTransaction{ 17 | Sender: sender, 18 | Blockhash: blockhash, 19 | TxBytes: transaction, 20 | Proof: proof, 21 | } 22 | } 23 | 24 | func (msg *MsgSubmitWithdrawTransaction) ValidateBasic() error { 25 | _, err := sdk.AccAddressFromBech32(msg.Sender) 26 | if err != nil { 27 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 28 | } 29 | 30 | if len(msg.Blockhash) == 0 { 31 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "blockhash cannot be empty") 32 | } 33 | 34 | if len(msg.TxBytes) == 0 { 35 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "transaction cannot be empty") 36 | } 37 | 38 | if len(msg.Proof) == 0 { 39 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "proof cannot be empty") 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /app/test_support.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" 5 | ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" 6 | 7 | "github.com/cosmos/cosmos-sdk/baseapp" 8 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 9 | bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" 10 | stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" 11 | 12 | btcbridgekeeper "github.com/sideprotocol/side/x/btcbridge/keeper" 13 | ) 14 | 15 | func (app *App) GetIBCKeeper() *ibckeeper.Keeper { 16 | return app.IBCKeeper 17 | } 18 | 19 | func (app *App) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { 20 | return app.ScopedIBCKeeper 21 | } 22 | 23 | func (app *App) GetBaseApp() *baseapp.BaseApp { 24 | return app.BaseApp 25 | } 26 | 27 | func (app *App) GetBankKeeper() bankkeeper.Keeper { 28 | return app.BankKeeper 29 | } 30 | 31 | func (app *App) GetStakingKeeper() *stakingkeeper.Keeper { 32 | return app.StakingKeeper 33 | } 34 | 35 | func (app *App) GetAccountKeeper() authkeeper.AccountKeeper { 36 | return app.AccountKeeper 37 | } 38 | 39 | func (app *App) GetBtcBridgeKeeper() btcbridgekeeper.Keeper { 40 | return app.BtcBridgeKeeper 41 | } 42 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/std" 7 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 8 | 9 | "github.com/sideprotocol/side/app/params" 10 | ) 11 | 12 | // makeEncodingConfig creates an EncodingConfig for an amino based test configuration. 13 | func makeEncodingConfig() params.EncodingConfig { 14 | amino := codec.NewLegacyAmino() 15 | interfaceRegistry := types.NewInterfaceRegistry() 16 | marshaler := codec.NewProtoCodec(interfaceRegistry) 17 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) 18 | 19 | return params.EncodingConfig{ 20 | InterfaceRegistry: interfaceRegistry, 21 | Codec: marshaler, 22 | TxConfig: txCfg, 23 | Amino: amino, 24 | } 25 | } 26 | 27 | // MakeEncodingConfig creates an EncodingConfig for testing 28 | func MakeEncodingConfig() params.EncodingConfig { 29 | encodingConfig := makeEncodingConfig() 30 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 31 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 32 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 33 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 34 | return encodingConfig 35 | } 36 | -------------------------------------------------------------------------------- /x/incentive/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | banktype "github.com/cosmos/cosmos-sdk/x/bank/types" 8 | ) 9 | 10 | // BankKeeper defines the expected interface needed to retrieve account balances. 11 | type BankKeeper interface { 12 | SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins 13 | // Methods imported from bank should be defined here 14 | 15 | SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 16 | 17 | SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error 18 | SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 19 | SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error 20 | SetDenomMetaData(ctx context.Context, denomMetaData banktype.Metadata) 21 | 22 | MintCoins(ctx context.Context, moduleName string, amounts sdk.Coins) error 23 | BurnCoins(ctx context.Context, moduleName string, amounts sdk.Coins) error 24 | 25 | HasSupply(ctx context.Context, denom string) bool 26 | GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin 27 | } 28 | -------------------------------------------------------------------------------- /proto/side/incentive/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.incentive; 3 | 4 | import "cosmos/msg/v1/msg.proto"; 5 | import "gogoproto/gogo.proto"; 6 | import "side/incentive/params.proto"; 7 | 8 | option go_package = "github.com/sideprotocol/side/x/incentive/types"; 9 | 10 | // Msg defines the Msg service. 11 | service Msg { 12 | option (cosmos.msg.v1.service) = true; 13 | 14 | // UpdateParams defines a governance operation for updating the x/incentive module 15 | // parameters. The authority defaults to the x/gov module account. 16 | // 17 | // Since: cosmos-sdk 0.47 18 | rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); 19 | } 20 | 21 | // MsgUpdateParams is the Msg/UpdateParams request type. 22 | // 23 | // Since: cosmos-sdk 0.47 24 | message MsgUpdateParams { 25 | option (cosmos.msg.v1.signer) = "authority"; 26 | 27 | // authority is the address that controls the module (defaults to x/gov unless overwritten). 28 | string authority = 1; 29 | 30 | // params defines the x/incentive parameters to be updated. 31 | // 32 | // NOTE: All parameters must be supplied. 33 | Params params = 2 [(gogoproto.nullable) = false]; 34 | } 35 | 36 | // MsgUpdateParamsResponse defines the Msg/UpdateParams response type. 37 | // 38 | // Since: cosmos-sdk 0.47 39 | message MsgUpdateParamsResponse {} 40 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_submit_deposit_transaction.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgSubmitDepositTransaction{} 9 | 10 | func NewMsgSubmitDepositTransaction( 11 | sender string, 12 | blockhash string, 13 | prevTx string, 14 | tx string, 15 | proof []string, 16 | ) *MsgSubmitDepositTransaction { 17 | return &MsgSubmitDepositTransaction{ 18 | Sender: sender, 19 | Blockhash: blockhash, 20 | PrevTxBytes: prevTx, 21 | TxBytes: tx, 22 | Proof: proof, 23 | } 24 | } 25 | 26 | func (msg *MsgSubmitDepositTransaction) ValidateBasic() error { 27 | _, err := sdk.AccAddressFromBech32(msg.Sender) 28 | if err != nil { 29 | return errorsmod.Wrapf(err, "invalid sender address (%s)", err) 30 | } 31 | 32 | if len(msg.Blockhash) == 0 { 33 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "blockhash cannot be empty") 34 | } 35 | 36 | if len(msg.PrevTxBytes) == 0 { 37 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "previous transaction cannot be empty") 38 | } 39 | 40 | if len(msg.TxBytes) == 0 { 41 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "transaction cannot be empty") 42 | } 43 | 44 | if len(msg.Proof) == 0 { 45 | return errorsmod.Wrap(ErrInvalidBtcTransaction, "proof cannot be empty") 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /x/incentive/keeper/queries.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | 11 | "github.com/sideprotocol/side/x/incentive/types" 12 | ) 13 | 14 | var _ types.QueryServer = Keeper{} 15 | 16 | func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { 17 | if req == nil { 18 | return nil, status.Error(codes.InvalidArgument, "invalid request") 19 | } 20 | 21 | ctx := sdk.UnwrapSDKContext(goCtx) 22 | 23 | return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil 24 | } 25 | 26 | func (k Keeper) Rewards(goCtx context.Context, req *types.QueryRewardsRequest) (*types.QueryRewardsResponse, error) { 27 | if req == nil { 28 | return nil, status.Error(codes.InvalidArgument, "invalid request") 29 | } 30 | 31 | ctx := sdk.UnwrapSDKContext(goCtx) 32 | 33 | return &types.QueryRewardsResponse{ 34 | Rewards: k.GetRewards(ctx, req.Address), 35 | }, nil 36 | } 37 | 38 | func (k Keeper) RewardStats(goCtx context.Context, req *types.QueryRewardStatsRequest) (*types.QueryRewardStatsResponse, error) { 39 | if req == nil { 40 | return nil, status.Error(codes.InvalidArgument, "invalid request") 41 | } 42 | 43 | ctx := sdk.UnwrapSDKContext(goCtx) 44 | 45 | return &types.QueryRewardStatsResponse{ 46 | RewardStats: k.GetRewardStats(ctx), 47 | }, nil 48 | } 49 | -------------------------------------------------------------------------------- /x/incentive/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "cosmossdk.io/log" 5 | storetypes "cosmossdk.io/store/types" 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | "github.com/sideprotocol/side/x/incentive/types" 10 | ) 11 | 12 | type Keeper struct { 13 | cdc codec.BinaryCodec 14 | storeKey storetypes.StoreKey 15 | memKey storetypes.StoreKey 16 | 17 | bankKeeper types.BankKeeper 18 | 19 | authority string 20 | } 21 | 22 | func NewKeeper( 23 | cdc codec.BinaryCodec, 24 | storeKey, 25 | memKey storetypes.StoreKey, 26 | bankKeeper types.BankKeeper, 27 | authority string, 28 | ) Keeper { 29 | return Keeper{ 30 | cdc: cdc, 31 | storeKey: storeKey, 32 | memKey: memKey, 33 | bankKeeper: bankKeeper, 34 | authority: authority, 35 | } 36 | } 37 | 38 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 39 | sdkCtx := sdk.UnwrapSDKContext(ctx) 40 | return sdkCtx.Logger().With("module", "x/"+types.ModuleName) 41 | } 42 | 43 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { 44 | store := ctx.KVStore(k.storeKey) 45 | 46 | bz := k.cdc.MustMarshal(¶ms) 47 | 48 | store.Set(types.ParamsStoreKey, bz) 49 | } 50 | 51 | func (k Keeper) GetParams(ctx sdk.Context) types.Params { 52 | store := ctx.KVStore(k.storeKey) 53 | 54 | var params types.Params 55 | bz := store.Get(types.ParamsStoreKey) 56 | k.cdc.MustUnmarshal(bz, ¶ms) 57 | 58 | return params 59 | } 60 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | build: 3 | binary: sided 4 | proto: 5 | path: proto 6 | third_party_paths: 7 | - third_party/proto 8 | - proto_vendor 9 | accounts: 10 | - name: alice 11 | coins: 12 | - 10000000000000000000000000000000uside 13 | - 10000000000000000000000000000000uusdc 14 | - 10000000000000000000000000000000uusdt 15 | 16 | - name: bob 17 | mnemonic: "furnace wild gravity resist heavy beef bundle deliver design service cycle monkey" 18 | coins: 19 | - 10000000000000000000000000000000uside 20 | - 10000000000000000000000000000000uusdc 21 | - 10000000000000000000000000000000uusdt 22 | # faucet: 23 | # name: bob 24 | # coins: 25 | # - 10uside 26 | # host: 0.0.0.0:4500 27 | genesis: 28 | app_state: 29 | crisis: 30 | constant_fee: 31 | denom: uside 32 | gov: 33 | deposit_params: 34 | min_deposit: 35 | - amount: "10000000" 36 | denom: uside 37 | params: 38 | min_deposit: 39 | - amount: "10000000" 40 | denom: uside 41 | voting_period: "60s" 42 | mint: 43 | params: 44 | mint_denom: uside 45 | staking: 46 | params: 47 | bond_denom: uside 48 | chain_id: grimoria-testnet-1 49 | consensus_params: 50 | block: 51 | max_gas: "100000000" 52 | validators: 53 | - name: alice 54 | bonded: 10000000000000000000000uside 55 | home: $HOME/.side 56 | client: 57 | openapi: 58 | path: "docs/static/openapi.yml" 59 | prefix: 60 | address: "side" 61 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_complete_dkg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | var _ sdk.Msg = &MsgCompleteDKG{} 12 | 13 | func NewMsgCompleteDKG( 14 | sender string, 15 | id uint64, 16 | vaults []string, 17 | consAddress string, 18 | signature string, 19 | ) *MsgCompleteDKG { 20 | return &MsgCompleteDKG{ 21 | Sender: sender, 22 | Id: id, 23 | Vaults: vaults, 24 | ConsensusAddress: consAddress, 25 | Signature: signature, 26 | } 27 | } 28 | 29 | // ValidateBasic performs basic MsgCompleteDKG message validation. 30 | func (m *MsgCompleteDKG) ValidateBasic() error { 31 | if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { 32 | return errorsmod.Wrap(err, "invalid sender address") 33 | } 34 | 35 | if len(m.Vaults) == 0 { 36 | return ErrInvalidDKGCompletionRequest 37 | } 38 | 39 | vaults := make(map[string]bool) 40 | for _, v := range m.Vaults { 41 | _, err := sdk.AccAddressFromBech32(v) 42 | if err != nil || vaults[v] { 43 | return ErrInvalidDKGCompletionRequest 44 | } 45 | 46 | vaults[v] = true 47 | } 48 | 49 | if _, err := sdk.ConsAddressFromHex(m.ConsensusAddress); err != nil { 50 | return ErrInvalidDKGCompletionRequest 51 | } 52 | 53 | sigBytes, err := hex.DecodeString(m.Signature) 54 | if err != nil { 55 | return ErrInvalidDKGCompletionRequest 56 | } 57 | 58 | if len(sigBytes) != ed25519.SignatureSize { 59 | return ErrInvalidDKGCompletionRequest 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /testutil/nullify/nullify.go: -------------------------------------------------------------------------------- 1 | // Package nullify provides methods to init nil values structs for test assertion. 2 | package nullify 3 | 4 | import ( 5 | "reflect" 6 | "unsafe" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | var ( 12 | coinType = reflect.TypeOf(sdk.Coin{}) 13 | coinsType = reflect.TypeOf(sdk.Coins{}) 14 | ) 15 | 16 | // Fill analyze all struct fields and slices with 17 | // reflection and initialize the nil and empty slices, 18 | // structs, and pointers. 19 | func Fill(x interface{}) interface{} { 20 | v := reflect.Indirect(reflect.ValueOf(x)) 21 | switch v.Kind() { 22 | case reflect.Slice: 23 | for i := 0; i < v.Len(); i++ { 24 | obj := v.Index(i) 25 | objPt := reflect.NewAt(obj.Type(), unsafe.Pointer(obj.UnsafeAddr())).Interface() 26 | objPt = Fill(objPt) 27 | obj.Set(reflect.ValueOf(objPt)) 28 | } 29 | case reflect.Struct: 30 | for i := 0; i < v.NumField(); i++ { 31 | f := reflect.Indirect(v.Field(i)) 32 | if !f.CanSet() { 33 | continue 34 | } 35 | switch f.Kind() { 36 | case reflect.Slice: 37 | f.Set(reflect.MakeSlice(f.Type(), 0, 0)) 38 | case reflect.Struct: 39 | switch f.Type() { 40 | case coinType: 41 | coin := reflect.New(coinType).Interface() 42 | s := reflect.ValueOf(coin).Elem() 43 | f.Set(s) 44 | case coinsType: 45 | coins := reflect.New(coinsType).Interface() 46 | s := reflect.ValueOf(coins).Elem() 47 | f.Set(s) 48 | default: 49 | objPt := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Interface() 50 | s := Fill(objPt) 51 | f.Set(reflect.ValueOf(s)) 52 | } 53 | } 54 | } 55 | } 56 | return reflect.Indirect(v).Interface() 57 | } 58 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_transfer_vault.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/btcsuite/btcd/btcutil/psbt" 7 | 8 | errorsmod "cosmossdk.io/errors" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 11 | ) 12 | 13 | var _ sdk.Msg = &MsgTransferVault{} 14 | 15 | // ValidateBasic performs basic MsgTransferVault message validation. 16 | func (m *MsgTransferVault) ValidateBasic() error { 17 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 18 | return errorsmod.Wrap(err, "invalid authority address") 19 | } 20 | 21 | if m.SourceVersion == m.DestVersion { 22 | return ErrInvalidVaultVersion 23 | } 24 | 25 | if m.AssetType == AssetType_ASSET_TYPE_UNSPECIFIED { 26 | return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "invalid asset type") 27 | } 28 | 29 | for _, p := range m.Psbts { 30 | packet, err := psbt.NewFromRawBytes(bytes.NewReader([]byte(p)), true) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if err := CheckTransactionWeight(packet.UnsignedTx, nil); err != nil { 36 | return err 37 | } 38 | 39 | for i, ti := range packet.UnsignedTx.TxIn { 40 | if ti.Sequence != MagicSequence { 41 | return ErrInvalidPsbt 42 | } 43 | 44 | if packet.Inputs[i].SighashType != DefaultSigHashType { 45 | return ErrInvalidPsbt 46 | } 47 | } 48 | 49 | for _, out := range packet.UnsignedTx.TxOut { 50 | if IsDustOut(out) { 51 | return ErrDustOutput 52 | } 53 | } 54 | } 55 | 56 | if len(m.Psbts) == 0 { 57 | if m.TargetUtxoNum == 0 { 58 | return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "target number of utxos must be greater than 0") 59 | } 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_initiate_dkg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/base64" 5 | "slices" 6 | 7 | errorsmod "cosmossdk.io/errors" 8 | "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 11 | ) 12 | 13 | var _ sdk.Msg = &MsgInitiateDKG{} 14 | 15 | // ValidateBasic performs basic MsgInitiateDKG message validation. 16 | func (m *MsgInitiateDKG) ValidateBasic() error { 17 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 18 | return errorsmod.Wrap(err, "invalid authority address") 19 | } 20 | 21 | if len(m.Participants) == 0 || m.Threshold == 0 || m.Threshold > uint32(len(m.Participants)) { 22 | return ErrInvalidDKGParams 23 | } 24 | 25 | participants := make(map[string]bool) 26 | 27 | for _, p := range m.Participants { 28 | if len(p.Moniker) > stakingtypes.MaxMonikerLength { 29 | return ErrInvalidDKGParams 30 | } 31 | 32 | if _, err := sdk.ValAddressFromBech32(p.OperatorAddress); err != nil { 33 | return errorsmod.Wrap(err, "invalid operator address") 34 | } 35 | 36 | if pubKey, err := base64.StdEncoding.DecodeString(p.ConsensusPubkey); err != nil || len(pubKey) != ed25519.PubKeySize { 37 | return errorsmod.Wrap(err, "invalid consensus public key") 38 | } 39 | 40 | if participants[p.ConsensusPubkey] { 41 | return errorsmod.Wrap(ErrInvalidDKGParams, "duplicate participant") 42 | } 43 | 44 | participants[p.ConsensusPubkey] = true 45 | } 46 | 47 | if !slices.Equal(m.VaultTypes, SupportedAssetTypes()) { 48 | return errorsmod.Wrap(ErrInvalidDKGParams, "incorrect vault types") 49 | } 50 | 51 | if m.EnableTransfer { 52 | if m.TargetUtxoNum == 0 { 53 | return errorsmod.Wrap(ErrInvalidDKGParams, "target number of utxos must be greater than 0") 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /x/btcbridge/module/genesis.go: -------------------------------------------------------------------------------- 1 | package btcbridge 2 | 3 | import ( 4 | "sort" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | "github.com/sideprotocol/side/x/btcbridge/keeper" 9 | "github.com/sideprotocol/side/x/btcbridge/types" 10 | ) 11 | 12 | // InitGenesis initializes the module's state from a provided genesis state. 13 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { 14 | // this line is used by starport scaffolding # genesis/module/init 15 | k.SetParams(ctx, genState.Params) 16 | 17 | // set the best block header 18 | k.SetBestBlockHeader(ctx, genState.BestBlockHeader) 19 | k.SetBlockHeader(ctx, genState.BestBlockHeader) 20 | 21 | // set block headers 22 | for _, header := range genState.BlockHeaders { 23 | k.SetBlockHeader(ctx, header) 24 | } 25 | 26 | // set utxos 27 | for _, utxo := range genState.Utxos { 28 | k.SaveUTXO(ctx, utxo) 29 | } 30 | 31 | // set dkg request 32 | if genState.DkgRequest != nil { 33 | k.SetDKGRequest(ctx, genState.DkgRequest) 34 | k.SetDKGRequestID(ctx, genState.DkgRequest.Id) 35 | } 36 | 37 | // sort vaults and set the latest vault version 38 | if len(genState.Params.Vaults) > 0 { 39 | vaults := genState.Params.Vaults 40 | sort.Slice(vaults, func(i, j int) bool { return vaults[i].Version < vaults[j].Version }) 41 | 42 | k.SetVaultVersion(ctx, vaults[len(vaults)-1].Version) 43 | } 44 | } 45 | 46 | // ExportGenesis returns the module's exported genesis 47 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 48 | genesis := types.DefaultGenesis() 49 | genesis.Params = k.GetParams(ctx) 50 | genesis.BestBlockHeader = k.GetBestBlockHeader(ctx) 51 | genesis.BlockHeaders = k.GetAllBlockHeaders(ctx) 52 | genesis.Utxos = k.GetAllUTXOs(ctx) 53 | 54 | // this line is used by starport scaffolding # genesis/module/export 55 | 56 | return genesis 57 | } 58 | -------------------------------------------------------------------------------- /x/btcbridge/types/msg_consolidate_vaults.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "lukechampine.com/uint128" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | var _ sdk.Msg = &MsgConsolidateVaults{} 11 | 12 | // ValidateBasic performs basic MsgConsolidateVaults message validation. 13 | func (m *MsgConsolidateVaults) ValidateBasic() error { 14 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 15 | return errorsmod.Wrap(err, "invalid authority address") 16 | } 17 | 18 | if m.BtcConsolidation == nil && len(m.RunesConsolidations) == 0 { 19 | return errorsmod.Wrap(ErrInvalidConsolidation, "neither btc nor runes consolidation provided") 20 | } 21 | 22 | if m.BtcConsolidation != nil { 23 | if err := ensureBtcConsolidation(m.BtcConsolidation); err != nil { 24 | return err 25 | } 26 | } 27 | 28 | if len(m.RunesConsolidations) != 0 { 29 | if err := ensureRunesConsolidations(m.RunesConsolidations); err != nil { 30 | return err 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // ensureBtcConsolidation checks the given btc consolidation 38 | func ensureBtcConsolidation(consolidation *BtcConsolidation) error { 39 | if consolidation.TargetThreshold <= 0 { 40 | return errorsmod.Wrap(ErrInvalidConsolidation, "btc target threshold must be greater than 0") 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // ensureRunesConsolidations checks the given runes consolidations 47 | func ensureRunesConsolidations(consolidations []*RunesConsolidation) error { 48 | for _, c := range consolidations { 49 | var id RuneId 50 | err := id.FromString(c.RuneId) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | threshold, err := uint128.FromString(c.TargetThreshold) 56 | if err != nil || threshold.IsZero() { 57 | return errorsmod.Wrap(ErrInvalidConsolidation, "invalid runes target threshold") 58 | } 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /proto/side/incentive/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.incentive; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "side/incentive/incentive.proto"; 7 | import "side/incentive/params.proto"; 8 | 9 | option go_package = "github.com/sideprotocol/side/x/incentive/types"; 10 | 11 | // Query defines the gRPC querier service. 12 | service Query { 13 | // Params queries the parameters of the module. 14 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 15 | option (google.api.http).get = "/side/incentive/params"; 16 | } 17 | // Rewards queries the rewards of the given address. 18 | rpc Rewards(QueryRewardsRequest) returns (QueryRewardsResponse) { 19 | option (google.api.http).get = "/side/incentive/rewards"; 20 | } 21 | // RewardStats queries total reward statistics. 22 | rpc RewardStats(QueryRewardStatsRequest) returns (QueryRewardStatsResponse) { 23 | option (google.api.http).get = "/side/incentive/rewards/stats"; 24 | } 25 | } 26 | 27 | // QueryRewardsRequest is request type for the Query/Rewards RPC method. 28 | message QueryRewardsRequest { 29 | string address = 1; 30 | } 31 | 32 | // QueryRewardsResponse is response type for the Query/Rewards RPC method. 33 | message QueryRewardsResponse { 34 | Rewards rewards = 1; 35 | } 36 | 37 | // QueryRewardStatsRequest is request type for the Query/RewardStats RPC method. 38 | message QueryRewardStatsRequest { 39 | } 40 | 41 | // QueryRewardStatsResponse is response type for the Query/RewardStats RPC method. 42 | message QueryRewardStatsResponse { 43 | RewardStats reward_stats = 1; 44 | } 45 | 46 | // QueryParamsRequest is request type for the Query/Params RPC method. 47 | message QueryParamsRequest {} 48 | 49 | // QueryParamsResponse is response type for the Query/Params RPC method. 50 | message QueryParamsResponse { 51 | // params holds all the parameters of this module. 52 | Params params = 1 [(gogoproto.nullable) = false]; 53 | } 54 | -------------------------------------------------------------------------------- /testutil/keeper/incentive.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "cosmossdk.io/log" 9 | "cosmossdk.io/store" 10 | "cosmossdk.io/store/metrics" 11 | storetypes "cosmossdk.io/store/types" 12 | cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" 13 | dbm "github.com/cosmos/cosmos-db" 14 | "github.com/cosmos/cosmos-sdk/codec" 15 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 16 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 17 | sdk "github.com/cosmos/cosmos-sdk/types" 18 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 19 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 20 | 21 | "github.com/sideprotocol/side/app" 22 | "github.com/sideprotocol/side/x/incentive/keeper" 23 | "github.com/sideprotocol/side/x/incentive/types" 24 | ) 25 | 26 | func IncentiveKeeper(t testing.TB) (keeper.Keeper, sdk.Context) { 27 | db := dbm.NewMemDB() 28 | 29 | app := app.New(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}) 30 | 31 | storeKey := storetypes.NewKVStoreKey(types.StoreKey) 32 | memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) 33 | 34 | stateStore := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) 35 | stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) 36 | stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) 37 | require.NoError(t, stateStore.LoadLatestVersion()) 38 | 39 | registry := codectypes.NewInterfaceRegistry() 40 | cdc := codec.NewProtoCodec(registry) 41 | 42 | authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() 43 | 44 | k := keeper.NewKeeper( 45 | cdc, 46 | storeKey, 47 | memStoreKey, 48 | app.BankKeeper, 49 | authority, 50 | ) 51 | 52 | ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) 53 | 54 | // Initialize params 55 | k.SetParams(ctx, types.DefaultParams()) 56 | 57 | return k, ctx 58 | } 59 | -------------------------------------------------------------------------------- /testutil/keeper/btc_bridge.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "cosmossdk.io/log" 9 | "cosmossdk.io/store" 10 | "cosmossdk.io/store/metrics" 11 | storetypes "cosmossdk.io/store/types" 12 | cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" 13 | dbm "github.com/cosmos/cosmos-db" 14 | "github.com/cosmos/cosmos-sdk/codec" 15 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 16 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 17 | sdk "github.com/cosmos/cosmos-sdk/types" 18 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 19 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 20 | 21 | "github.com/sideprotocol/side/app" 22 | "github.com/sideprotocol/side/x/btcbridge/keeper" 23 | "github.com/sideprotocol/side/x/btcbridge/types" 24 | ) 25 | 26 | func BtcBridgeKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { 27 | db := dbm.NewMemDB() 28 | 29 | app := app.New(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}) 30 | 31 | storeKey := storetypes.NewKVStoreKey(types.StoreKey) 32 | memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) 33 | 34 | stateStore := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) 35 | stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) 36 | stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) 37 | require.NoError(t, stateStore.LoadLatestVersion()) 38 | 39 | registry := codectypes.NewInterfaceRegistry() 40 | cdc := codec.NewProtoCodec(registry) 41 | 42 | authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() 43 | 44 | k := keeper.NewKeeper( 45 | cdc, 46 | storeKey, 47 | memStoreKey, 48 | app.BankKeeper, 49 | app.StakingKeeper, 50 | app.IncentiveKeeper, 51 | authority, 52 | ) 53 | 54 | ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) 55 | 56 | // Initialize params 57 | k.SetParams(ctx, types.DefaultParams()) 58 | 59 | return k, ctx 60 | } 61 | -------------------------------------------------------------------------------- /x/incentive/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 6 | 7 | "github.com/sideprotocol/side/x/incentive/types" 8 | ) 9 | 10 | // IncentiveEnabled returns true if the incentive mechanism is enabled, false otherwise 11 | func (k Keeper) IncentiveEnabled(ctx sdk.Context) bool { 12 | return k.GetParams(ctx).Enabled && !k.bankKeeper.SpendableCoins(ctx, authtypes.NewModuleAddress(types.ModuleName)).IsZero() 13 | } 14 | 15 | // DepositIncentiveEnabled returns true if the incentive is enabled for deposit, false otherwise 16 | func (k Keeper) DepositIncentiveEnabled(ctx sdk.Context) bool { 17 | return k.IncentiveEnabled(ctx) && k.RewardPerDeposit(ctx).IsPositive() && k.bankKeeper.GetBalance(ctx, authtypes.NewModuleAddress(types.ModuleName), k.DepositRewardDenom(ctx)).IsPositive() 18 | } 19 | 20 | // WithdrawIncentiveEnabled returns true if the incentive is enabled for withdrawal, false otherwise 21 | func (k Keeper) WithdrawIncentiveEnabled(ctx sdk.Context) bool { 22 | return k.IncentiveEnabled(ctx) && k.RewardPerWithdraw(ctx).IsPositive() && k.bankKeeper.GetBalance(ctx, authtypes.NewModuleAddress(types.ModuleName), k.WithdrawRewardDenom(ctx)).IsPositive() 23 | } 24 | 25 | // RewardPerDeposit returns the reward amount for each deposit 26 | func (k Keeper) RewardPerDeposit(ctx sdk.Context) sdk.Coin { 27 | return k.GetParams(ctx).RewardPerDeposit 28 | } 29 | 30 | // RewardPerWithdraw returns the reward amount for each withdrawal 31 | func (k Keeper) RewardPerWithdraw(ctx sdk.Context) sdk.Coin { 32 | return k.GetParams(ctx).RewardPerWithdraw 33 | } 34 | 35 | // DepositRewardDenom returns the denom for deposit reward 36 | func (k Keeper) DepositRewardDenom(ctx sdk.Context) string { 37 | return k.GetParams(ctx).RewardPerDeposit.Denom 38 | } 39 | 40 | // WithdrawRewardDenom returns the denom for withdrawal reward 41 | func (k Keeper) WithdrawRewardDenom(ctx sdk.Context) string { 42 | return k.GetParams(ctx).RewardPerWithdraw.Denom 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sidechain 2 | **sidechain** is a blockchain built using Cosmos SDK and Tendermint and created with [Ignite CLI](https://ignite.com/cli). 3 | 4 | ## Get started 5 | 6 | ``` 7 | ignite chain serve 8 | ``` 9 | `serve` command installs dependencies, builds, initializes, and starts your blockchain in development. 10 | 11 | ### Requirements 12 | 13 | - `ignite version: 0.27.1` 14 | - `go version >= 1.20.x` 15 | 16 | ### Configure 17 | 18 | Your blockchain in development can be configured with `config.yml`. To learn more, see the [Ignite CLI docs](https://docs.ignite.com). 19 | 20 | ### Web Frontend 21 | 22 | Ignite CLI has scaffolded a Vue.js-based web app in the `vue` directory. Run the following commands to install dependencies and start the app: 23 | 24 | ``` 25 | cd vue 26 | npm install 27 | npm run serve 28 | ``` 29 | 30 | The frontend app is built using the `@starport/vue` and `@starport/vuex` packages. For details, see the [monorepo for Ignite front-end development](https://github.com/ignite/web). 31 | 32 | ## Release 33 | To release a new version of your blockchain, create and push a new tag with `v` prefix. A new draft release with the configured targets will be created. 34 | 35 | ``` 36 | git tag v0.1 37 | git push origin v0.1 38 | ``` 39 | 40 | After a draft release is created, make your final changes from the release page and publish it. 41 | 42 | ### Install 43 | To install the latest version of your blockchain node's binary, execute the following command on your machine: 44 | 45 | ``` 46 | curl https://get.ignite.com/username/sidechain@latest! | sudo bash 47 | ``` 48 | `username/sidechain` should match the `username` and `repo_name` of the Github repository to which the source code was pushed. Learn more about [the install process](https://github.com/allinbits/starport-installer). 49 | 50 | ## Learn more 51 | 52 | - [Ignite CLI](https://ignite.com/cli) 53 | - [Tutorials](https://docs.ignite.com/guide) 54 | - [Ignite CLI docs](https://docs.ignite.com) 55 | - [Cosmos SDK docs](https://docs.cosmos.network) 56 | - [Developer Chat](https://discord.gg/ignite) 57 | -------------------------------------------------------------------------------- /x/btcbridge/types/runes_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/btcsuite/btcd/wire" 10 | 11 | "github.com/sideprotocol/side/x/btcbridge/types" 12 | ) 13 | 14 | func TestParseRunes(t *testing.T) { 15 | testCases := []struct { 16 | name string 17 | pkScriptHex string 18 | edicts []*types.Edict 19 | expectPass bool 20 | }{ 21 | { 22 | name: "valid runes edict", 23 | pkScriptHex: "6a5d0b00c0a2330380cab5ee0101", 24 | edicts: []*types.Edict{ 25 | { 26 | Id: &types.RuneId{Block: 840000, Tx: 3}, 27 | Amount: "500000000", 28 | Output: 1, 29 | }, 30 | }, 31 | expectPass: true, 32 | }, 33 | { 34 | name: "output index is out of range", 35 | pkScriptHex: "6a5d0b00c0a2330380cab5ee0102", 36 | expectPass: false, 37 | }, 38 | { 39 | name: "no OP_RETURN", 40 | pkScriptHex: "615d0b00c0a2330380cab5ee0102", 41 | expectPass: true, 42 | edicts: nil, 43 | }, 44 | { 45 | name: "no runes magic number", 46 | pkScriptHex: "6a5c0b00c0a2330380cab5ee0102", 47 | expectPass: true, 48 | edicts: nil, 49 | }, 50 | { 51 | name: "non data push op", 52 | pkScriptHex: "6a5d4f00c0a2330380cab5ee0102", 53 | expectPass: false, 54 | }, 55 | { 56 | name: "no tag body for edicts", 57 | pkScriptHex: "6a5d0b01c0a2330380cab5ee0102", 58 | expectPass: false, 59 | }, 60 | { 61 | name: "invalid edict", 62 | pkScriptHex: "6a5d0b00c0a2330380cab5ee01", 63 | expectPass: false, 64 | }, 65 | } 66 | 67 | for _, tc := range testCases { 68 | t.Run(tc.name, func(t *testing.T) { 69 | pkScript, err := hex.DecodeString(tc.pkScriptHex) 70 | require.NoError(t, err) 71 | 72 | tx := wire.NewMsgTx(types.TxVersion) 73 | tx.AddTxOut(wire.NewTxOut(0, pkScript)) 74 | 75 | edicts, err := types.ParseRunes(tx) 76 | if tc.expectPass { 77 | require.NoError(t, err) 78 | require.EqualValues(t, tc.edicts, edicts) 79 | } else { 80 | require.NotNil(t, err) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /x/btcbridge/types/varint.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "lukechampine.com/uint128" 8 | ) 9 | 10 | func EncodeUint32(n uint32) []byte { 11 | var result []byte 12 | 13 | for n >= 128 { 14 | result = append(result, byte(n&0x7F|0x80)) 15 | n >>= 7 16 | } 17 | 18 | result = append(result, byte(n)) 19 | return result 20 | } 21 | 22 | func EncodeUint64(n uint64) []byte { 23 | var result []byte 24 | 25 | for n >= 128 { 26 | result = append(result, byte(n&0x7F|0x80)) 27 | n >>= 7 28 | } 29 | 30 | result = append(result, byte(n)) 31 | return result 32 | } 33 | 34 | func EncodeUint128(n *uint128.Uint128) []byte { 35 | return EncodeBigInt(n.Big()) 36 | } 37 | 38 | func EncodeBigInt(n *big.Int) []byte { 39 | var result []byte 40 | 41 | for n.Cmp(big.NewInt(128)) >= 0 { 42 | temp := new(big.Int).Set(n) 43 | last := temp.And(n, new(big.Int).SetUint64(0b0111_1111)) 44 | result = append(result, last.Or(last, new(big.Int).SetUint64(0b1000_0000)).Bytes()[0]) 45 | n.Rsh(n, 7) 46 | } 47 | 48 | if len(n.Bytes()) == 0 { 49 | result = append(result, 0) 50 | } else { 51 | result = append(result, n.Bytes()...) 52 | } 53 | 54 | return result 55 | } 56 | 57 | func Decode(bz []byte) (uint128.Uint128, int, error) { 58 | n := big.NewInt(0) 59 | 60 | for i, b := range bz { 61 | if i > 18 { 62 | return uint128.Zero, 0, errors.New("varint overflow") 63 | } 64 | 65 | value := uint64(b) & 0b0111_1111 66 | if i == 18 && value&0b0111_1100 != 0 { 67 | return uint128.Zero, 0, errors.New("varint too large") 68 | } 69 | 70 | temp := new(big.Int).SetUint64(value) 71 | n.Or(n, temp.Lsh(temp, uint(7*i))) 72 | 73 | if b&0b1000_0000 == 0 { 74 | return uint128.FromBig(n), i + 1, nil 75 | } 76 | } 77 | 78 | return uint128.Zero, 0, errors.New("varint too short") 79 | } 80 | 81 | func DecodeVec(payload []byte) ([]uint128.Uint128, error) { 82 | vec := make([]uint128.Uint128, 0) 83 | i := 0 84 | 85 | for i < len(payload) { 86 | value, length, err := Decode(payload[i:]) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | vec = append(vec, value) 92 | i += length 93 | } 94 | 95 | return vec, nil 96 | } 97 | -------------------------------------------------------------------------------- /x/btcbridge/types/tss.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/base64" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "reflect" 9 | "strings" 10 | 11 | "github.com/cometbft/cometbft/crypto" 12 | "github.com/cometbft/cometbft/crypto/tmhash" 13 | ) 14 | 15 | // MustGetConsensusAddr gets the hex-encoded consensus address from the given consensus public key 16 | // Panic if any error occurs 17 | func MustGetConsensusAddr(consPubKey string) string { 18 | pubKey, err := base64.StdEncoding.DecodeString(consPubKey) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | return hex.EncodeToString(tmhash.SumTruncated(pubKey)) 24 | } 25 | 26 | // ParticipantExists returns true if the given address is a participant, false otherwise 27 | func ParticipantExists(participants []*DKGParticipant, consAddress string) bool { 28 | for _, p := range participants { 29 | if MustGetConsensusAddr(p.ConsensusPubkey) == strings.ToLower(consAddress) { 30 | return true 31 | } 32 | } 33 | 34 | return false 35 | } 36 | 37 | // CheckDKGCompletionRequests checks if the vaults of all the DKG completion requests are same 38 | func CheckDKGCompletionRequests(requests []*DKGCompletionRequest) bool { 39 | if len(requests) == 0 { 40 | return false 41 | } 42 | 43 | vaults := requests[0].Vaults 44 | 45 | for _, req := range requests[1:] { 46 | if !reflect.DeepEqual(req.Vaults, vaults) { 47 | return false 48 | } 49 | } 50 | 51 | return true 52 | } 53 | 54 | // VerifySignature verifies the given signature against the given DKG completion request 55 | func VerifySignature(signature string, pubKey []byte, req *DKGCompletionRequest) bool { 56 | sig, err := hex.DecodeString(signature) 57 | if err != nil { 58 | return false 59 | } 60 | 61 | sigMsg := GetSigMsgFromDKGCompletionReq(req) 62 | 63 | return ed25519.Verify(pubKey, sigMsg, sig) 64 | } 65 | 66 | // GetSigMsgFromDKGCompletionReq gets the msg to be signed from the given DKG completion request 67 | func GetSigMsgFromDKGCompletionReq(req *DKGCompletionRequest) []byte { 68 | rawMsg := make([]byte, 8) 69 | binary.BigEndian.PutUint64(rawMsg, req.Id) 70 | 71 | for _, v := range req.Vaults { 72 | rawMsg = append(rawMsg, []byte(v)...) 73 | } 74 | 75 | return crypto.Sha256(rawMsg) 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow is useful if you want to automate the process of: 2 | # 3 | # a) Creating a new prelease when you push a new tag with a "v" prefix (version). 4 | # 5 | # This type of prerelease is meant to be used for production: alpha, beta, rc, etc. types of releases. 6 | # After the prerelease is created, you need to make your changes on the release page at the relevant 7 | # Github page and publish your release. 8 | # 9 | # b) Creating/updating the "latest" prerelease when you push to your default branch. 10 | # 11 | # This type of prelease is useful to make your bleeding-edge binaries available to advanced users. 12 | # 13 | # The workflow will not run if there is no tag pushed with a "v" prefix and no change pushed to your 14 | # default branch. 15 | on: push 16 | 17 | jobs: 18 | might_release: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Prepare Release Variables 27 | id: vars 28 | uses: ignite/cli/actions/release/vars@main 29 | 30 | - name: Issue Release Assets 31 | uses: ignite/cli/actions/cli@main 32 | if: ${{ steps.vars.outputs.should_release == 'true' }} 33 | with: 34 | args: chain build --release --release.prefix ${{ steps.vars.outputs.tarball_prefix }} -t linux:amd64 -t darwin:amd64 -t darwin:arm64 35 | env: 36 | DO_NOT_TRACK: 1 37 | GOFLAGS: "-buildvcs=false" 38 | continue-on-error: true 39 | 40 | - name: Delete the "latest" Release 41 | uses: dev-drprasad/delete-tag-and-release@v0.2.1 42 | if: ${{ steps.vars.outputs.is_release_type_latest == 'true' }} 43 | with: 44 | tag_name: ${{ steps.vars.outputs.tag_name }} 45 | delete_release: true 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | - name: Publish the Release 50 | uses: softprops/action-gh-release@v1 51 | if: ${{ steps.vars.outputs.should_release == 'true' }} 52 | with: 53 | tag_name: ${{ steps.vars.outputs.tag_name }} 54 | files: release/* 55 | prerelease: true 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /x/btcbridge/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/x/auth/types" 8 | banktype "github.com/cosmos/cosmos-sdk/x/bank/types" 9 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 10 | ) 11 | 12 | // AccountKeeper defines the expected account keeper used for simulations (noalias) 13 | type AccountKeeper interface { 14 | GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI 15 | // Methods imported from account should be defined here 16 | } 17 | 18 | // BankKeeper defines the expected interface needed to retrieve account balances. 19 | type BankKeeper interface { 20 | SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins 21 | // Methods imported from bank should be defined here 22 | 23 | SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 24 | 25 | SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error 26 | SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 27 | SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error 28 | SetDenomMetaData(ctx context.Context, denomMetaData banktype.Metadata) 29 | 30 | MintCoins(ctx context.Context, moduleName string, amounts sdk.Coins) error 31 | BurnCoins(ctx context.Context, moduleName string, amounts sdk.Coins) error 32 | 33 | HasSupply(ctx context.Context, denom string) bool 34 | GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin 35 | } 36 | 37 | // StakingKeeper defines the expected staking keeper used to retrieve validator (noalias) 38 | type StakingKeeper interface { 39 | GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) 40 | GetValidatorByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (stakingtypes.Validator, error) 41 | } 42 | 43 | // IncentiveKeeper defines the expected incentive keeper 44 | type IncentiveKeeper interface { 45 | DepositIncentiveEnabled(ctx sdk.Context) bool 46 | WithdrawIncentiveEnabled(ctx sdk.Context) bool 47 | 48 | DistributeDepositReward(ctx sdk.Context, addr string) error 49 | DistributeWithdrawReward(ctx sdk.Context, addr string) error 50 | } 51 | -------------------------------------------------------------------------------- /x/btcbridge/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/msgservice" 8 | ) 9 | 10 | func RegisterCodec(cdc *codec.LegacyAmino) { 11 | cdc.RegisterConcrete(&MsgSubmitBlockHeaders{}, "btcbridge/MsgSubmitBlockHeaders", nil) 12 | cdc.RegisterConcrete(&MsgSubmitDepositTransaction{}, "btcbridge/MsgSubmitDepositTransaction", nil) 13 | cdc.RegisterConcrete(&MsgSubmitWithdrawTransaction{}, "btcbridge/MsgSubmitWithdrawTransaction", nil) 14 | cdc.RegisterConcrete(&MsgSubmitFeeRate{}, "btcbridge/MsgSubmitFeeRate", nil) 15 | cdc.RegisterConcrete(&MsgUpdateTrustedNonBtcRelayers{}, "btcbridge/MsgUpdateTrustedNonBtcRelayers", nil) 16 | cdc.RegisterConcrete(&MsgUpdateTrustedFeeProviders{}, "btcbridge/MsgUpdateTrustedFeeProviders", nil) 17 | cdc.RegisterConcrete(&MsgWithdrawToBitcoin{}, "btcbridge/MsgWithdrawToBitcoin", nil) 18 | cdc.RegisterConcrete(&MsgSubmitSignatures{}, "btcbridge/MsgSubmitSignatures", nil) 19 | cdc.RegisterConcrete(&MsgCompleteDKG{}, "btcbridge/MsgCompleteDKG", nil) 20 | cdc.RegisterConcrete(&MsgUpdateParams{}, "btcbridge/MsgUpdateParams", nil) 21 | // this line is used by starport scaffolding # 2 22 | } 23 | 24 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { 25 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitBlockHeaders{}) 26 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitDepositTransaction{}) 27 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitWithdrawTransaction{}) 28 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitFeeRate{}) 29 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateTrustedNonBtcRelayers{}) 30 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateTrustedFeeProviders{}) 31 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgWithdrawToBitcoin{}) 32 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitSignatures{}) 33 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgCompleteDKG{}) 34 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateParams{}) 35 | // this line is used by starport scaffolding # 3 36 | 37 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 38 | } 39 | 40 | var ( 41 | Amino = codec.NewLegacyAmino() 42 | ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) 43 | ) 44 | -------------------------------------------------------------------------------- /x/btcbridge/codec/bech32_codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "cosmossdk.io/core/address" 8 | errorsmod "cosmossdk.io/errors" 9 | 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | "github.com/cosmos/cosmos-sdk/types/bech32" 12 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 13 | ) 14 | 15 | type bech32Codec struct { 16 | nativeBech32Prefix string 17 | bitcoinBech32Prefix string 18 | } 19 | 20 | var _ address.Codec = &bech32Codec{} 21 | 22 | // NewBech32Codec creates a new address codec 23 | func NewBech32Codec(nativeBech32Prefix string, bitcoinBech32Prefix string) address.Codec { 24 | return bech32Codec{nativeBech32Prefix, bitcoinBech32Prefix} 25 | } 26 | 27 | // StringToBytes encodes text to bytes 28 | func (bc bech32Codec) StringToBytes(text string) ([]byte, error) { 29 | if len(strings.TrimSpace(text)) == 0 { 30 | return []byte{}, errors.New("empty address string is not allowed") 31 | } 32 | 33 | hrp, bz, err := bech32.DecodeAndConvert(text) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | if hrp != bc.nativeBech32Prefix && hrp != bc.bitcoinBech32Prefix { 39 | return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "hrp does not match bech32 prefixes: expected '%s or %s' got '%s'", bc.nativeBech32Prefix, bc.bitcoinBech32Prefix, hrp) 40 | } 41 | 42 | if err := sdk.VerifyAddressFormat(bz); err != nil { 43 | return nil, err 44 | } 45 | 46 | return bz, nil 47 | } 48 | 49 | // BytesToString decodes bytes to text 50 | func (bc bech32Codec) BytesToString(bz []byte) (string, error) { 51 | bech32Prefix := bc.nativeBech32Prefix 52 | if isBitcoinAddress(bz) { 53 | bech32Prefix = bc.bitcoinBech32Prefix 54 | } 55 | 56 | text, err := bech32.ConvertAndEncode(bech32Prefix, bz) 57 | if err != nil { 58 | return "", err 59 | } 60 | 61 | return text, nil 62 | } 63 | 64 | // isBitcoinAddress returns true if the given address is segwit or taproot, false otherwise 65 | func isBitcoinAddress(address []byte) bool { 66 | return isSegwitAddress(address) || isTaprootAddress(address) 67 | } 68 | 69 | // isSegwitAddress returns true if the given address is segwit, false otherwise 70 | func isSegwitAddress(address []byte) bool { 71 | return len(address) == 33 // bech32 decoded address length 72 | } 73 | 74 | // isTaprootAddress returns true if the given address is taproot, false otherwise 75 | func isTaprootAddress(address []byte) bool { 76 | return len(address) == 32 // only with taproot output key 77 | } 78 | -------------------------------------------------------------------------------- /cmd/sided/cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | cmtcfg "github.com/cometbft/cometbft/config" 5 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 6 | ) 7 | 8 | // initCometBFTConfig helps to override default CometBFT Config values. 9 | // return cmtcfg.DefaultConfig if no custom configuration is required for the application. 10 | func initCometBFTConfig() *cmtcfg.Config { 11 | cfg := cmtcfg.DefaultConfig() 12 | 13 | // these values put a higher strain on node memory 14 | // cfg.P2P.MaxNumInboundPeers = 100 15 | // cfg.P2P.MaxNumOutboundPeers = 40 16 | 17 | return cfg 18 | } 19 | 20 | // initAppConfig helps to override default appConfig template and configs. 21 | // return "", nil if no custom configuration is required for the application. 22 | func initAppConfig() (string, interface{}) { 23 | // The following code snippet is just for reference. 24 | type CustomAppConfig struct { 25 | serverconfig.Config `mapstructure:",squash"` 26 | } 27 | 28 | // Optionally allow the chain developer to overwrite the SDK's default 29 | // server config. 30 | srvCfg := serverconfig.DefaultConfig() 31 | // The SDK's default minimum gas price is set to "" (empty value) inside 32 | // app.toml. If left empty by validators, the node will halt on startup. 33 | // However, the chain developer can set a default app.toml value for their 34 | // validators here. 35 | // 36 | // In summary: 37 | // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their 38 | // own app.toml config, 39 | // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their 40 | // own app.toml to override, or use this default value. 41 | // 42 | // In tests, we set the min gas prices to 0. 43 | // srvCfg.MinGasPrices = "0stake" 44 | // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default 45 | 46 | customAppConfig := CustomAppConfig{ 47 | Config: *srvCfg, 48 | } 49 | 50 | customAppTemplate := serverconfig.DefaultConfigTemplate 51 | // Edit the default template file 52 | // 53 | // customAppTemplate := serverconfig.DefaultConfigTemplate + ` 54 | // [wasm] 55 | // # This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries 56 | // query_gas_limit = 300000 57 | // # This is the number of wasm vm instances we keep cached in memory for speed-up 58 | // # Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally 59 | // lru_size = 0` 60 | 61 | return customAppTemplate, customAppConfig 62 | } 63 | -------------------------------------------------------------------------------- /x/incentive/client/cli/query.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | // "strings" 7 | 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/cosmos/cosmos-sdk/client" 11 | "github.com/cosmos/cosmos-sdk/client/flags" 12 | 13 | "github.com/sideprotocol/side/x/incentive/types" 14 | ) 15 | 16 | // GetQueryCmd returns the cli query commands for this module 17 | func GetQueryCmd(_ string) *cobra.Command { 18 | // Group yield queries under a subcommand 19 | cmd := &cobra.Command{ 20 | Use: types.ModuleName, 21 | Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), 22 | DisableFlagParsing: true, 23 | SuggestionsMinimumDistance: 2, 24 | RunE: client.ValidateCmd, 25 | } 26 | 27 | cmd.AddCommand(CmdQueryParams()) 28 | cmd.AddCommand(CmdQueryRewards()) 29 | cmd.AddCommand(CmdQueryRewardStats()) 30 | // this line is used by starport scaffolding # 1 31 | 32 | return cmd 33 | } 34 | 35 | func CmdQueryParams() *cobra.Command { 36 | cmd := &cobra.Command{ 37 | Use: "params", 38 | Short: "Query the parameters of the module", 39 | Args: cobra.NoArgs, 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | clientCtx, err := client.GetClientQueryContext(cmd) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | queryClient := types.NewQueryClient(clientCtx) 47 | 48 | res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return clientCtx.PrintProto(res) 54 | }, 55 | } 56 | 57 | flags.AddQueryFlagsToCmd(cmd) 58 | 59 | return cmd 60 | } 61 | 62 | func CmdQueryRewards() *cobra.Command { 63 | cmd := &cobra.Command{ 64 | Use: "rewards", 65 | Short: "Query the rewards of the given address", 66 | Args: cobra.ExactArgs(1), 67 | RunE: func(cmd *cobra.Command, args []string) error { 68 | clientCtx, err := client.GetClientQueryContext(cmd) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | queryClient := types.NewQueryClient(clientCtx) 74 | 75 | res, err := queryClient.Rewards(cmd.Context(), &types.QueryRewardsRequest{ 76 | Address: args[0], 77 | }) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return clientCtx.PrintProto(res) 83 | }, 84 | } 85 | 86 | flags.AddQueryFlagsToCmd(cmd) 87 | 88 | return cmd 89 | } 90 | 91 | func CmdQueryRewardStats() *cobra.Command { 92 | cmd := &cobra.Command{ 93 | Use: "reward-stats", 94 | Short: "Query total reward statistics", 95 | Args: cobra.NoArgs, 96 | RunE: func(cmd *cobra.Command, args []string) error { 97 | clientCtx, err := client.GetClientQueryContext(cmd) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | queryClient := types.NewQueryClient(clientCtx) 103 | 104 | res, err := queryClient.RewardStats(cmd.Context(), &types.QueryRewardStatsRequest{}) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | return clientCtx.PrintProto(res) 110 | }, 111 | } 112 | 113 | flags.AddQueryFlagsToCmd(cmd) 114 | 115 | return cmd 116 | } 117 | -------------------------------------------------------------------------------- /x/btcbridge/types/block_header.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "math/big" 5 | time "time" 6 | 7 | "github.com/btcsuite/btcd/blockchain" 8 | "github.com/btcsuite/btcd/chaincfg/chainhash" 9 | "github.com/btcsuite/btcd/wire" 10 | 11 | errorsmod "cosmossdk.io/errors" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | ) 14 | 15 | // Validate validates the block header 16 | func (header *BlockHeader) Validate() error { 17 | wireHeader := header.ToWireHeader() 18 | 19 | if err := blockchain.CheckBlockHeaderSanity( 20 | wireHeader, 21 | sdk.GetConfig().GetBtcChainCfg().PowLimit, 22 | blockchain.NewMedianTime(), 23 | blockchain.BFNone, 24 | ); err != nil { 25 | return errorsmod.Wrapf(ErrInvalidBlockHeader, "check failed: %v", err) 26 | } 27 | 28 | if header.Hash != wireHeader.BlockHash().String() { 29 | return errorsmod.Wrap(ErrInvalidBlockHeader, "incorrect block hash") 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // ToWireHeader converts the block header to wire.BlockHeader 36 | func (header *BlockHeader) ToWireHeader() *wire.BlockHeader { 37 | prevBlockHash, _ := chainhash.NewHashFromStr(header.PreviousBlockHash) 38 | merkleRoot, _ := chainhash.NewHashFromStr(header.MerkleRoot) 39 | 40 | bits := new(big.Int) 41 | bits.SetString(header.Bits, 16) 42 | 43 | return &wire.BlockHeader{ 44 | Version: int32(header.Version), 45 | PrevBlock: *prevBlockHash, 46 | MerkleRoot: *merkleRoot, 47 | Timestamp: time.Unix(int64(header.Time), 0), 48 | Bits: uint32(bits.Uint64()), 49 | Nonce: uint32(header.Nonce), 50 | } 51 | } 52 | 53 | // GetWork gets the work of the block header 54 | func (header *BlockHeader) GetWork() *big.Int { 55 | return blockchain.CalcWork(BitsToTargetUint32(header.Bits)) 56 | } 57 | 58 | // BlockHeaders defines a set of block headers which form a chain 59 | type BlockHeaders []*BlockHeader 60 | 61 | // Validate validates if each block header is valid and if the block headers form a chain 62 | func (headers BlockHeaders) Validate() error { 63 | if len(headers) == 0 { 64 | return errorsmod.Wrap(ErrInvalidBlockHeaders, "block headers can not be empty") 65 | } 66 | 67 | var lastHeight uint64 68 | var lastHash string 69 | 70 | for i, h := range headers { 71 | if err := h.Validate(); err != nil { 72 | return err 73 | } 74 | 75 | if i > 0 && h.Height != lastHeight+1 && h.PreviousBlockHash != lastHash { 76 | return errorsmod.Wrap(ErrInvalidBlockHeaders, "block headers can not form a chain") 77 | } 78 | 79 | lastHeight = h.Height 80 | lastHash = h.Hash 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // GetTotalWork gets the total work of the block headers 87 | func (headers BlockHeaders) GetTotalWork() *big.Int { 88 | totalWork := new(big.Int) 89 | 90 | for _, h := range headers { 91 | work := h.GetWork() 92 | totalWork = new(big.Int).Add(totalWork, work) 93 | } 94 | 95 | return totalWork 96 | } 97 | 98 | func BitsToTarget(bits string) *big.Int { 99 | n := new(big.Int) 100 | n.SetString(bits, 16) 101 | 102 | return n 103 | } 104 | 105 | func BitsToTargetUint32(bits string) uint32 { 106 | return uint32(BitsToTarget(bits).Uint64()) 107 | } 108 | -------------------------------------------------------------------------------- /testutil/network/network.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | pruningtypes "cosmossdk.io/store/pruning/types" 9 | tmrand "github.com/cometbft/cometbft/libs/rand" 10 | dbm "github.com/cosmos/cosmos-db" 11 | "github.com/cosmos/cosmos-sdk/baseapp" 12 | "github.com/cosmos/cosmos-sdk/crypto/hd" 13 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 14 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 15 | "github.com/cosmos/cosmos-sdk/testutil/network" 16 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 17 | sdk "github.com/cosmos/cosmos-sdk/types" 18 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 19 | "github.com/stretchr/testify/require" 20 | 21 | "github.com/sideprotocol/side/app" 22 | ) 23 | 24 | type ( 25 | Network = network.Network 26 | Config = network.Config 27 | ) 28 | 29 | // New creates instance with fully configured cosmos network. 30 | // Accepts optional config, that will be used in place of the DefaultConfig() if provided. 31 | func New(t *testing.T, configs ...Config) *Network { 32 | if len(configs) > 1 { 33 | panic("at most one config should be provided") 34 | } 35 | var cfg network.Config 36 | if len(configs) == 0 { 37 | cfg = DefaultConfig() 38 | } else { 39 | cfg = configs[0] 40 | } 41 | net, err := network.New(t, t.TempDir(), cfg) 42 | require.NoError(t, err) 43 | _, err = net.WaitForHeight(1) 44 | require.NoError(t, err) 45 | t.Cleanup(net.Cleanup) 46 | return net 47 | } 48 | 49 | // DefaultConfig will initialize config for the network with custom application, 50 | // genesis and single validator. All other parameters are inherited from cosmos-sdk/testutil/network.DefaultConfig 51 | func DefaultConfig() network.Config { 52 | var ( 53 | encoding = app.MakeEncodingConfig() 54 | chainID = "chain-" + tmrand.NewRand().Str(6) 55 | ) 56 | return network.Config{ 57 | Codec: encoding.Codec, 58 | TxConfig: encoding.TxConfig, 59 | LegacyAmino: encoding.Amino, 60 | InterfaceRegistry: encoding.InterfaceRegistry, 61 | AccountRetriever: authtypes.AccountRetriever{}, 62 | AppConstructor: func(val network.ValidatorI) servertypes.Application { 63 | return app.New( 64 | val.GetCtx().Logger, 65 | dbm.NewMemDB(), 66 | nil, 67 | true, 68 | simtestutil.EmptyAppOptions{}, 69 | baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), 70 | baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices), 71 | baseapp.SetChainID(chainID), 72 | ) 73 | }, 74 | GenesisState: app.ModuleBasics.DefaultGenesis(encoding.Codec), 75 | TimeoutCommit: 2 * time.Second, 76 | ChainID: chainID, 77 | NumValidators: 1, 78 | BondDenom: sdk.DefaultBondDenom, 79 | MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), 80 | AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), 81 | StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), 82 | BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), 83 | PruningStrategy: pruningtypes.PruningOptionNothing, 84 | CleanupDir: true, 85 | SigningAlgo: string(hd.Secp256k1Type), 86 | KeyringOptions: []keyring.Option{}, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /third_node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | KEYS=("dev0" "dev1") 4 | CHAINID="testnet" 5 | MONIKER="Side Labs" 6 | BINARY="$HOME/go/bin/sided" 7 | DENOM_STR="uside,uusdc" 8 | 9 | set -f 10 | IFS=, 11 | DENOMS=($DENOM_STR) 12 | 13 | IFS=";" 14 | 15 | 16 | INITIAL_SUPPLY="500000000000000" 17 | BLOCK_GAS=10000000 18 | MAX_GAS=10000000000 19 | 20 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world, 21 | # otherwise your balance will be wiped quickly 22 | # The keyring test does not require private key to steal tokens from you 23 | KEYRING="test" 24 | #KEYALGO="secp256k1" 25 | KEYALGO="segwit" 26 | LOGLEVEL="info" 27 | # Set dedicated home directory for the $BINARY instance 28 | HOMEDIR="$HOME/.side3" 29 | 30 | # Path variables 31 | CONFIG=$HOMEDIR/config/config.toml 32 | APP_TOML=$HOMEDIR/config/app.toml 33 | GENESIS=$HOMEDIR/config/genesis.json 34 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json 35 | PEER_ID="peer" 36 | 37 | # validate dependencies are installed 38 | command -v jq >/dev/null 2>&1 || { 39 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/" 40 | exit 1 41 | } 42 | 43 | # used to exit on first error (any non-zero exit code) 44 | set -e 45 | 46 | # Reinstall daemon 47 | # make install 48 | 49 | # User prompt if an existing local node configuration is found. 50 | if [ -d "$HOMEDIR" ]; then 51 | printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR" 52 | echo "Overwrite the existing configuration and start a new local node? [y/n]" 53 | read -r overwrite 54 | else 55 | overwrite="Y" 56 | fi 57 | 58 | 59 | # Setup local node if overwrite is set to Yes, otherwise skip setup 60 | if [[ $overwrite == "y" || $overwrite == "Y" ]]; then 61 | # Remove the previous folder 62 | rm -rf "$HOMEDIR" 63 | 64 | $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR" 65 | 66 | # Set client config 67 | cp -r ~/.side/config/genesis.json $HOMEDIR/config/genesis.json 68 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR" 69 | $BINARY config chain-id $CHAINID --home "$HOMEDIR" 70 | 71 | sed -i.bak 's/127.0.0.1:26657/0.0.0.0:36657/g' "$CONFIG" 72 | sed -i.bak 's/127.0.0.1:26658/0.0.0.0:36658/g' "$CONFIG" 73 | sed -i.bak 's/0.0.0.0:26656/0.0.0.0:36656/g' "$CONFIG" 74 | sed -i.bak 's/persistent_peers = ""/persistent_peers = "dfd3e3c99414aa850f6e269cf4a674a66062cd49@127.0.0.1:26656"/g' "$CONFIG" 75 | #sed -i 's/persistent_peers = "$PEERID"/g' "$CONFIG" 76 | 77 | sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML 78 | sed -i.bak 's/localhost:9090/localhost:8090/g' $APP_TOML 79 | 80 | $BINARY keys add secp256k1_key --key-type segwit --home "$HOMEDIR" 81 | $BINARY tx bank send dev1 $($BINARY keys show secp256k1_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes 82 | sleep 6 83 | $BINARY keys add segwit_key --key-type segwit --home "$HOMEDIR" 84 | $BINARY tx bank send dev1 $($BINARY keys show segwit_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes 85 | 86 | 87 | fi 88 | 89 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed) 90 | $BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} --home "$HOMEDIR" --pruning="everything" 91 | -------------------------------------------------------------------------------- /second_node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | KEYS=("dev0" "dev1") 4 | CHAINID="testnet" 5 | MONIKER="Side Labs" 6 | BINARY="$HOME/go/bin/sided" 7 | DENOM_STR="uside,uusdc" 8 | 9 | set -f 10 | IFS=, 11 | DENOMS=($DENOM_STR) 12 | 13 | IFS=";" 14 | 15 | 16 | INITIAL_SUPPLY="500000000000000" 17 | BLOCK_GAS=10000000 18 | MAX_GAS=10000000000 19 | 20 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world, 21 | # otherwise your balance will be wiped quickly 22 | # The keyring test does not require private key to steal tokens from you 23 | KEYRING="test" 24 | #KEYALGO="secp256k1" 25 | KEYALGO="segwit" 26 | LOGLEVEL="info" 27 | # Set dedicated home directory for the $BINARY instance 28 | HOMEDIR="$HOME/.side2" 29 | 30 | # Path variables 31 | CONFIG=$HOMEDIR/config/config.toml 32 | APP_TOML=$HOMEDIR/config/app.toml 33 | GENESIS=$HOMEDIR/config/genesis.json 34 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json 35 | PEER_ID="peer" 36 | 37 | # validate dependencies are installed 38 | command -v jq >/dev/null 2>&1 || { 39 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/" 40 | exit 1 41 | } 42 | 43 | # used to exit on first error (any non-zero exit code) 44 | set -e 45 | 46 | # Reinstall daemon 47 | # make install 48 | 49 | # User prompt if an existing local node configuration is found. 50 | if [ -d "$HOMEDIR" ]; then 51 | printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR" 52 | echo "Overwrite the existing configuration and start a new local node? [y/n]" 53 | read -r overwrite 54 | else 55 | overwrite="Y" 56 | fi 57 | 58 | 59 | # Setup local node if overwrite is set to Yes, otherwise skip setup 60 | if [[ $overwrite == "y" || $overwrite == "Y" ]]; then 61 | # Remove the previous folder 62 | rm -rf "$HOMEDIR" 63 | 64 | $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR" 65 | 66 | # Set client config 67 | cp -r ~/.side/config/genesis.json $HOMEDIR/config/genesis.json 68 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR" 69 | $BINARY config chain-id $CHAINID --home "$HOMEDIR" 70 | 71 | sed -i.bak 's/127.0.0.1:26657/0.0.0.0:16657/g' "$CONFIG" 72 | sed -i.bak 's/127.0.0.1:26658/0.0.0.0:16658/g' "$CONFIG" 73 | sed -i.bak 's/0.0.0.0:26656/0.0.0.0:16656/g' "$CONFIG" 74 | sed -i.bak 's/persistent_peers = ""/persistent_peers = "dfd3e3c99414aa850f6e269cf4a674a66062cd49@127.0.0.1:26656"/g' "$CONFIG" 75 | #sed -i 's/persistent_peers = "$PEERID"/g' "$CONFIG" 76 | 77 | sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML 78 | sed -i.bak 's/localhost:9090/localhost:7090/g' $APP_TOML 79 | 80 | $BINARY keys add secp256k1_key --key-type segwit --home "$HOMEDIR" 81 | $BINARY tx bank send dev1 $($BINARY keys show secp256k1_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes 82 | sleep 6 83 | $BINARY keys add segwit_key --key-type segwit --home "$HOMEDIR" 84 | $BINARY tx bank send dev1 $($BINARY keys show segwit_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes 85 | 86 | 87 | fi 88 | 89 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed) 90 | $BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} --home "$HOMEDIR" --pruning="everything" 91 | -------------------------------------------------------------------------------- /x/btcbridge/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | // this line is used by starport scaffolding # genesis/types/import 11 | 12 | func DefaultBestBlockHeader() *BlockHeader { 13 | config := sdk.GetConfig().GetBtcChainCfg() 14 | switch config.Name { 15 | case chaincfg.MainNetParams.Name: 16 | return DefaultMainNetBestBlockHeader() 17 | case chaincfg.SigNetParams.Name: 18 | return DefaultSignetBestBlockHeader() 19 | } 20 | return DefaultTestnetBestBlockHeader() 21 | } 22 | 23 | func DefaultSignetBestBlockHeader() *BlockHeader { 24 | // testnet3 block 2815023 25 | return &BlockHeader{ 26 | Version: 536870912, 27 | Hash: "0000017317a7dfa637773406765d308e93cb5a8e5e266bb21687e120bf0e13d3", 28 | Height: 1, 29 | PreviousBlockHash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", 30 | MerkleRoot: "f1192075c6416b02df1487f1f302d925a875bec7e37cf38079e00b1cd831898a", 31 | Time: 1718707794, 32 | Bits: "1e0377ae", 33 | Nonce: 13603325, 34 | Ntx: 1, 35 | } 36 | } 37 | 38 | func DefaultTestnetBestBlockHeader() *BlockHeader { 39 | // testnet3 block 2815023 40 | return &BlockHeader{ 41 | Version: 667459584, 42 | Hash: "0000000000000009fb68da72e8994f014fafb455c72978233b94580b12af778c", 43 | Height: 2815023, 44 | PreviousBlockHash: "0000000000000004a29c20eb32532718de8072665620edb4c657b22b4d463967", 45 | MerkleRoot: "9e219423eadce80e882cdff04b3026c9bbc994fd08a774f34a705ca3e710a332", 46 | Time: 1715566066, 47 | Bits: "191881b8", 48 | Nonce: 3913166971, 49 | Ntx: 6236, 50 | } 51 | } 52 | 53 | func DefaultMainNetBestBlockHeader() *BlockHeader { 54 | // testnet3 block 2815023 55 | return &BlockHeader{ 56 | Version: 667459584, 57 | Hash: "0000000000000009fb68da72e8994f014fafb455c72978233b94580b12af778c", 58 | Height: 2815023, 59 | PreviousBlockHash: "0000000000000004a29c20eb32532718de8072665620edb4c657b22b4d463967", 60 | MerkleRoot: "9e219423eadce80e882cdff04b3026c9bbc994fd08a774f34a705ca3e710a332", 61 | Time: 1715566066, 62 | Bits: "191881b8", 63 | Nonce: 3913166971, 64 | Ntx: 6236, 65 | } 66 | } 67 | 68 | // DefaultGenesis returns the default genesis state 69 | func DefaultGenesis() *GenesisState { 70 | return &GenesisState{ 71 | // this line is used by starport scaffolding # genesis/types/default 72 | Params: DefaultParams(), 73 | BestBlockHeader: DefaultBestBlockHeader(), 74 | BlockHeaders: []*BlockHeader{}, 75 | Utxos: []*UTXO{}, 76 | DkgRequest: nil, 77 | } 78 | } 79 | 80 | // Validate performs basic genesis state validation returning an error upon any 81 | // failure. 82 | func (gs GenesisState) Validate() error { 83 | // this line is used by starport scaffolding # genesis/types/validate 84 | 85 | // validate the best block header 86 | if gs.BestBlockHeader == nil { 87 | return errorsmod.Wrap(ErrInvalidBlockHeader, "best block header can not be empty") 88 | } 89 | if err := gs.BestBlockHeader.Validate(); err != nil { 90 | return err 91 | } 92 | 93 | // validate block headers 94 | if len(gs.BlockHeaders) != 0 { 95 | if err := BlockHeaders(gs.BlockHeaders).Validate(); err != nil { 96 | return err 97 | } 98 | } 99 | 100 | // validate params 101 | return gs.Params.Validate() 102 | } 103 | -------------------------------------------------------------------------------- /x/btcbridge/types/signature.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | secp256k1 "github.com/btcsuite/btcd/btcec/v2" 5 | "github.com/btcsuite/btcd/btcec/v2/ecdsa" 6 | "github.com/btcsuite/btcd/btcec/v2/schnorr" 7 | "github.com/btcsuite/btcd/btcutil/psbt" 8 | "github.com/btcsuite/btcd/txscript" 9 | "github.com/btcsuite/btcd/wire" 10 | ) 11 | 12 | // VerifyPsbtSignatures verifies the signatures of the given psbt 13 | // Note: assume that the psbt is finalized and all inputs are witness type 14 | func VerifyPsbtSignatures(p *psbt.Packet) bool { 15 | // extract signed tx 16 | signedTx, err := psbt.Extract(p) 17 | if err != nil { 18 | return false 19 | } 20 | 21 | // build previous output fetcher 22 | prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil) 23 | 24 | for i, txIn := range p.UnsignedTx.TxIn { 25 | prevOutput := p.Inputs[i].WitnessUtxo 26 | if prevOutput == nil { 27 | return false 28 | } 29 | 30 | prevOutputFetcher.AddPrevOut(txIn.PreviousOutPoint, prevOutput) 31 | } 32 | 33 | // verify signatures 34 | for i := range p.Inputs { 35 | prevOutput := p.Inputs[i].WitnessUtxo 36 | hashType := DefaultSigHashType 37 | 38 | switch { 39 | case txscript.IsPayToWitnessPubKeyHash(prevOutput.PkScript): 40 | if !verifyWitnessSignature(signedTx, i, prevOutput, prevOutputFetcher, hashType) { 41 | return false 42 | } 43 | 44 | case txscript.IsPayToTaproot(prevOutput.PkScript): 45 | if !verifyTaprootSignature(signedTx, i, prevOutput, prevOutputFetcher, hashType) { 46 | return false 47 | } 48 | 49 | default: 50 | return false 51 | } 52 | } 53 | 54 | return true 55 | } 56 | 57 | // verifyWitnessSignature verifies the signature of the witness v0 input 58 | func verifyWitnessSignature(tx *wire.MsgTx, idx int, prevOutput *wire.TxOut, prevOutputFetcher txscript.PrevOutputFetcher, hashType txscript.SigHashType) bool { 59 | witness := tx.TxIn[idx].Witness 60 | if len(witness) != 2 { 61 | return false 62 | } 63 | 64 | sigBytes := witness[0] 65 | pkBytes := witness[1] 66 | 67 | sig, err := ecdsa.ParseDERSignature(sigBytes) 68 | if err != nil { 69 | return false 70 | } 71 | 72 | pk, err := secp256k1.ParsePubKey(pkBytes) 73 | if err != nil { 74 | return false 75 | } 76 | 77 | if sigBytes[len(sigBytes)-1] != byte(hashType) { 78 | return false 79 | } 80 | 81 | sigHash, err := txscript.CalcWitnessSigHash(prevOutput.PkScript, txscript.NewTxSigHashes(tx, prevOutputFetcher), 82 | hashType, tx, idx, prevOutput.Value) 83 | if err != nil { 84 | return false 85 | } 86 | 87 | return sig.Verify(sigHash, pk) 88 | } 89 | 90 | // verifyTaprootSignature verifies the signature of the taproot input 91 | func verifyTaprootSignature(tx *wire.MsgTx, idx int, prevOutput *wire.TxOut, prevOutputFetcher txscript.PrevOutputFetcher, hashType txscript.SigHashType) bool { 92 | witness := tx.TxIn[idx].Witness 93 | if len(witness) != 1 || len(witness[0]) == 0 { 94 | return false 95 | } 96 | 97 | sigBytes := witness[0] 98 | 99 | if hashType != txscript.SigHashDefault { 100 | if sigBytes[len(sigBytes)-1] != byte(hashType) { 101 | return false 102 | } 103 | 104 | sigBytes = sigBytes[0 : len(sigBytes)-1] 105 | } 106 | 107 | sig, err := schnorr.ParseSignature(sigBytes) 108 | if err != nil { 109 | return false 110 | } 111 | 112 | pk, err := schnorr.ParsePubKey(prevOutput.PkScript[2:34]) 113 | if err != nil { 114 | return false 115 | } 116 | 117 | sigHash, err := txscript.CalcTaprootSignatureHash(txscript.NewTxSigHashes(tx, prevOutputFetcher), 118 | hashType, tx, idx, prevOutputFetcher) 119 | if err != nil { 120 | return false 121 | } 122 | 123 | return sig.Verify(sigHash, pk) 124 | } 125 | -------------------------------------------------------------------------------- /x/btcbridge/module/genesis_test.go: -------------------------------------------------------------------------------- 1 | package btcbridge_test 2 | 3 | // Path: x/btcbridge/genesis_test.go 4 | 5 | import ( 6 | "bytes" 7 | "encoding/hex" 8 | "testing" 9 | 10 | "github.com/btcsuite/btcd/btcutil" 11 | "github.com/btcsuite/btcd/wire" 12 | keepertest "github.com/sideprotocol/side/testutil/keeper" 13 | "github.com/sideprotocol/side/testutil/nullify" 14 | btcbridge "github.com/sideprotocol/side/x/btcbridge/module" 15 | "github.com/sideprotocol/side/x/btcbridge/types" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestGenesis(t *testing.T) { 20 | mnemonic := "sunny bamboo garlic fold reopen exile letter addict forest vessel square lunar shell number deliver cruise calm artist fire just kangaroo suit wheel extend" 21 | println(mnemonic) 22 | 23 | genesisState := types.DefaultGenesis() 24 | 25 | k, ctx := keepertest.BtcBridgeKeeper(t) 26 | btcbridge.InitGenesis(ctx, *k, *genesisState) 27 | got := btcbridge.ExportGenesis(ctx, *k) 28 | require.NotNil(t, got) 29 | 30 | nullify.Fill(&genesisState) 31 | nullify.Fill(got) 32 | 33 | // this line is used by starport scaffolding # genesis/test/assert 34 | } 35 | 36 | // TestSubmitTx tests the SubmitTx function 37 | // func TestSubmitTx(t *testing.T) { 38 | 39 | // // test tx: https://blockchain.info/tx/b657e22827039461a9493ede7bdf55b01579254c1630b0bfc9185ec564fc05ab?format=json 40 | 41 | // k, ctx := keepertest.BtcbridgeClientKeeper(t) 42 | 43 | // txHex := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00" 44 | // prevTxHex := "0200000000010183372652f2af9ab34b3a003efada6b054c75583185ac130de72599dfdf4e462b0100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d001076a64ee50500000000160014a03614eef338681373de94a2dc2574de55da1980024730440220274250f6036bea0947daf4455ab4976f81721257d163fd952fb5b0c70470edc602202fba816be260219bbc40a8983c459cf05cf2209bf1e62e7ccbf78aec54db607f0121031cee21ef69fe68b240c3032616fa310c6a60a856c0a7e0c1298815c92fb2c61788fb2a00" 45 | 46 | // msg := &types.MsgSubmitTransactionRequest{ 47 | // Sender: "", 48 | // Blockhash: "000000000d73ecf25d3bf8e6ae65c35aa2a90e3271edff8bab90d87ed875f13b", 49 | // TxBytes: "0100000001b3f7", 50 | // PrevTxBytes: "0100000001b3f7", 51 | // Proof: []string{"0100000001b3f7"}, 52 | // } 53 | // // this line is used by starport scaffolding # handler/test/submit 54 | // err := k.ProcessBitcoinDepositTransaction(ctx, msg) 55 | // require.NoError(t, err) 56 | // } 57 | 58 | // Decode transaction 59 | func TestDecodeTransaction(t *testing.T) { 60 | hexStr := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00" 61 | 62 | // Decode the hex string to transaction 63 | txBytes, err := hex.DecodeString(hexStr) 64 | require.NoError(t, err) 65 | 66 | // Create a new transaction 67 | var tx wire.MsgTx 68 | err = tx.Deserialize(bytes.NewReader(txBytes)) 69 | require.NoError(t, err) 70 | 71 | uTx := btcutil.NewTx(&tx) 72 | 73 | for _, input := range uTx.MsgTx().TxIn { 74 | t.Log(input.PreviousOutPoint.String()) 75 | } 76 | 77 | require.GreaterOrEqual(t, len(uTx.MsgTx().TxIn), 1) 78 | } 79 | -------------------------------------------------------------------------------- /proto/side/btcbridge/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.btcbridge; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/protobuf/duration.proto"; 6 | import "cosmos/base/v1beta1/coin.proto"; 7 | 8 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types"; 9 | 10 | // Params defines the parameters for the module. 11 | message Params { 12 | // The minimum number of confirmations required for the deposit transactions 13 | int32 deposit_confirmation_depth = 1; 14 | // The minimum number of confirmations required for the withdrawal transactions 15 | int32 withdraw_confirmation_depth = 2; 16 | // The allowed maximum depth for bitcoin block reorganization 17 | int32 max_reorg_depth = 3; 18 | // Indicates the maximum depth or distance from the latest block up to which transactions are considered for acceptance. 19 | uint64 max_acceptable_block_depth = 4; 20 | // The denomination of the voucher 21 | string btc_voucher_denom = 5; 22 | // Indicates if deposit is enabled 23 | bool deposit_enabled = 6; 24 | // Indicates if withdrawal is enabled 25 | bool withdraw_enabled = 7; 26 | // Trusted relayers to submit bitcoin block headers 27 | repeated string trusted_btc_relayers = 8; 28 | // Trusted relayers for non-btc asset deposit 29 | repeated string trusted_non_btc_relayers = 9; 30 | // Trusted fee providers to submit bitcoin fee rate 31 | repeated string trusted_fee_providers = 10; 32 | // Period of validity for the fee rate 33 | int64 fee_rate_validity_period = 11; 34 | // Asset vaults 35 | repeated Vault vaults = 12; 36 | // Withdrawal params 37 | WithdrawParams withdraw_params = 13 [(gogoproto.nullable) = false]; 38 | // Protocol limitations 39 | ProtocolLimits protocol_limits = 14 [(gogoproto.nullable) = false]; 40 | // Protocol fees 41 | ProtocolFees protocol_fees = 15 [(gogoproto.nullable) = false]; 42 | // TSS params 43 | TSSParams tss_params = 16 [(gogoproto.nullable) = false]; 44 | } 45 | 46 | // AssetType defines the type of asset 47 | enum AssetType { 48 | // Unspecified asset type 49 | ASSET_TYPE_UNSPECIFIED = 0; 50 | // BTC 51 | ASSET_TYPE_BTC = 1; 52 | // BRC20: ordi, sats 53 | ASSET_TYPE_BRC20 = 2; 54 | // RUNE: dog•go•to•the•moon 55 | ASSET_TYPE_RUNES = 3; 56 | } 57 | 58 | // Vault defines the asset vault 59 | message Vault { 60 | // the vault address for deposit 61 | string address = 1; 62 | // public key of the vault 63 | string pub_key = 2; 64 | // the asset type supported by the vault 65 | AssetType asset_type = 3; 66 | // version 67 | uint64 version = 4; 68 | } 69 | 70 | message WithdrawParams { 71 | // Maximum number of utxos used to build the signing request; O means unlimited 72 | uint32 max_utxo_num = 1; 73 | // Period for handling btc withdrawal requests 74 | int64 btc_batch_withdraw_period = 2; 75 | // Maximum number of btc withdrawal requests to be handled per batch 76 | uint32 max_btc_batch_withdraw_num = 3; 77 | } 78 | 79 | // ProtocolLimits defines the params related to the the protocol limitations 80 | message ProtocolLimits { 81 | // The minimum deposit amount for btc in sat 82 | int64 btc_min_deposit = 1; 83 | // The minimum withdrawal amount for btc in sat 84 | int64 btc_min_withdraw = 2; 85 | // The maximum withdrawal amount for btc in sat 86 | int64 btc_max_withdraw = 3; 87 | } 88 | 89 | // ProtocolFees defines the params related to the protocol fees 90 | message ProtocolFees { 91 | // Protocol fee amount for deposit in sat 92 | int64 deposit_fee = 1; 93 | // Protocol fee amount for withdrawal in sat 94 | int64 withdraw_fee = 2; 95 | // Protocol fee collector 96 | string collector = 3; 97 | } 98 | 99 | // TSSParams defines the params related to TSS 100 | message TSSParams { 101 | // Timeout duration for DKG request 102 | google.protobuf.Duration dkg_timeout_period = 1 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; 103 | // Transition period after which TSS participants update process is completed 104 | google.protobuf.Duration participant_update_transition_period = 2 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; 105 | } 106 | -------------------------------------------------------------------------------- /x/btcbridge/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/sideprotocol/side/x/btcbridge/types" 7 | ) 8 | 9 | // DepositConfirmationDepth gets the confirmation depth for deposit transactions 10 | func (k Keeper) DepositConfirmationDepth(ctx sdk.Context) int32 { 11 | return k.GetParams(ctx).DepositConfirmationDepth 12 | } 13 | 14 | // WithdrawConfirmationDepth gets the confirmation depth for withdrawal transactions 15 | func (k Keeper) WithdrawConfirmationDepth(ctx sdk.Context) int32 { 16 | return k.GetParams(ctx).WithdrawConfirmationDepth 17 | } 18 | 19 | // MaxReorgDepth gets the allowed maximum reorg depth 20 | func (k Keeper) MaxReorgDepth(ctx sdk.Context) int32 { 21 | return k.GetParams(ctx).MaxReorgDepth 22 | } 23 | 24 | // DepositEnabled returns true if deposit enabled, false otherwise 25 | func (k Keeper) DepositEnabled(ctx sdk.Context) bool { 26 | return k.GetParams(ctx).DepositEnabled 27 | } 28 | 29 | // WithdrawEnabled returns true if withdrawal enabled, false otherwise 30 | func (k Keeper) WithdrawEnabled(ctx sdk.Context) bool { 31 | return k.GetParams(ctx).WithdrawEnabled 32 | } 33 | 34 | // ProtocolDepositFeeEnabled returns true if the protocol fee is required for deposit, false otherwise 35 | func (k Keeper) ProtocolDepositFeeEnabled(ctx sdk.Context) bool { 36 | return k.GetParams(ctx).ProtocolFees.DepositFee > 0 37 | } 38 | 39 | // ProtocolWithdrawFeeEnabled returns true if the protocol fee is required for withdrawal, false otherwise 40 | func (k Keeper) ProtocolWithdrawFeeEnabled(ctx sdk.Context) bool { 41 | return k.GetParams(ctx).ProtocolFees.WithdrawFee > 0 42 | } 43 | 44 | // ProtocolFeeCollector gets the protocol fee collector 45 | func (k Keeper) ProtocolFeeCollector(ctx sdk.Context) string { 46 | return k.GetParams(ctx).ProtocolFees.Collector 47 | } 48 | 49 | // BtcDenom gets the btc denomination 50 | func (k Keeper) BtcDenom(ctx sdk.Context) string { 51 | return k.GetParams(ctx).BtcVoucherDenom 52 | } 53 | 54 | // IsTrustedBtcRelayer returns true if the given address is a trusted btc relayer, false otherwise 55 | func (k Keeper) IsTrustedBtcRelayer(ctx sdk.Context, addr string) bool { 56 | trustedBtcRelayers := k.GetParams(ctx).TrustedBtcRelayers 57 | if len(trustedBtcRelayers) == 0 { 58 | return true 59 | } 60 | 61 | for _, relayer := range trustedBtcRelayers { 62 | if relayer == addr { 63 | return true 64 | } 65 | } 66 | 67 | return false 68 | } 69 | 70 | // IsTrustedNonBtcRelayer returns true if the given address is a trusted non-btc relayer, false otherwise 71 | func (k Keeper) IsTrustedNonBtcRelayer(ctx sdk.Context, addr string) bool { 72 | for _, relayer := range k.GetParams(ctx).TrustedNonBtcRelayers { 73 | if relayer == addr { 74 | return true 75 | } 76 | } 77 | 78 | return false 79 | } 80 | 81 | // IsTrustedFeeProvider returns true if the given address is a trusted fee provider, false otherwise 82 | func (k Keeper) IsTrustedFeeProvider(ctx sdk.Context, addr string) bool { 83 | for _, provider := range k.GetParams(ctx).TrustedFeeProviders { 84 | if provider == addr { 85 | return true 86 | } 87 | } 88 | 89 | return false 90 | } 91 | 92 | // GetVaultByAssetTypeAndVersion gets the vault by the given asset type and version 93 | func (k Keeper) GetVaultByAssetTypeAndVersion(ctx sdk.Context, assetType types.AssetType, version uint64) *types.Vault { 94 | for _, v := range k.GetParams(ctx).Vaults { 95 | if v.AssetType == assetType && v.Version == version { 96 | return v 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // GetVaultVersionByAddress gets the vault version of the given address 104 | func (k Keeper) GetVaultVersionByAddress(ctx sdk.Context, address string) (uint64, bool) { 105 | for _, v := range k.GetParams(ctx).Vaults { 106 | if v.Address == address { 107 | return v.Version, true 108 | } 109 | } 110 | 111 | return 0, false 112 | } 113 | 114 | // GetMaxUtxoNum gets the maximum utxo number for the signing request 115 | func (k Keeper) GetMaxUtxoNum(ctx sdk.Context) int { 116 | params := k.GetParams(ctx) 117 | 118 | return int(params.WithdrawParams.MaxUtxoNum) 119 | } 120 | -------------------------------------------------------------------------------- /api/side/incentive/tx_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: side/incentive/tx.proto 6 | 7 | package incentive 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Msg_UpdateParams_FullMethodName = "/side.incentive.Msg/UpdateParams" 23 | ) 24 | 25 | // MsgClient is the client API for Msg service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type MsgClient interface { 29 | // UpdateParams defines a governance operation for updating the x/incentive module 30 | // parameters. The authority defaults to the x/gov module account. 31 | // 32 | // Since: cosmos-sdk 0.47 33 | UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) 34 | } 35 | 36 | type msgClient struct { 37 | cc grpc.ClientConnInterface 38 | } 39 | 40 | func NewMsgClient(cc grpc.ClientConnInterface) MsgClient { 41 | return &msgClient{cc} 42 | } 43 | 44 | func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { 45 | out := new(MsgUpdateParamsResponse) 46 | err := c.cc.Invoke(ctx, Msg_UpdateParams_FullMethodName, in, out, opts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return out, nil 51 | } 52 | 53 | // MsgServer is the server API for Msg service. 54 | // All implementations must embed UnimplementedMsgServer 55 | // for forward compatibility 56 | type MsgServer interface { 57 | // UpdateParams defines a governance operation for updating the x/incentive module 58 | // parameters. The authority defaults to the x/gov module account. 59 | // 60 | // Since: cosmos-sdk 0.47 61 | UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) 62 | mustEmbedUnimplementedMsgServer() 63 | } 64 | 65 | // UnimplementedMsgServer must be embedded to have forward compatible implementations. 66 | type UnimplementedMsgServer struct { 67 | } 68 | 69 | func (UnimplementedMsgServer) UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { 70 | return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") 71 | } 72 | func (UnimplementedMsgServer) mustEmbedUnimplementedMsgServer() {} 73 | 74 | // UnsafeMsgServer may be embedded to opt out of forward compatibility for this service. 75 | // Use of this interface is not recommended, as added methods to MsgServer will 76 | // result in compilation errors. 77 | type UnsafeMsgServer interface { 78 | mustEmbedUnimplementedMsgServer() 79 | } 80 | 81 | func RegisterMsgServer(s grpc.ServiceRegistrar, srv MsgServer) { 82 | s.RegisterService(&Msg_ServiceDesc, srv) 83 | } 84 | 85 | func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 86 | in := new(MsgUpdateParams) 87 | if err := dec(in); err != nil { 88 | return nil, err 89 | } 90 | if interceptor == nil { 91 | return srv.(MsgServer).UpdateParams(ctx, in) 92 | } 93 | info := &grpc.UnaryServerInfo{ 94 | Server: srv, 95 | FullMethod: Msg_UpdateParams_FullMethodName, 96 | } 97 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 98 | return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) 99 | } 100 | return interceptor(ctx, in, info, handler) 101 | } 102 | 103 | // Msg_ServiceDesc is the grpc.ServiceDesc for Msg service. 104 | // It's only intended for direct use with grpc.RegisterService, 105 | // and not to be introspected or modified (even as a copy) 106 | var Msg_ServiceDesc = grpc.ServiceDesc{ 107 | ServiceName: "side.incentive.Msg", 108 | HandlerType: (*MsgServer)(nil), 109 | Methods: []grpc.MethodDesc{ 110 | { 111 | MethodName: "UpdateParams", 112 | Handler: _Msg_UpdateParams_Handler, 113 | }, 114 | }, 115 | Streams: []grpc.StreamDesc{}, 116 | Metadata: "side/incentive/tx.proto", 117 | } 118 | -------------------------------------------------------------------------------- /x/incentive/keeper/reward.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/sideprotocol/side/x/incentive/types" 7 | ) 8 | 9 | // GetRewards gets the rewards of the given address 10 | func (k Keeper) GetRewards(ctx sdk.Context, address string) *types.Rewards { 11 | store := ctx.KVStore(k.storeKey) 12 | 13 | var rewards types.Rewards 14 | bz := store.Get(types.RewardsKey(address)) 15 | k.cdc.MustUnmarshal(bz, &rewards) 16 | 17 | return &rewards 18 | } 19 | 20 | // HasRewards returns true if the given address has received rewards, false otherwise 21 | func (k Keeper) HasRewards(ctx sdk.Context, address string) bool { 22 | store := ctx.KVStore(k.storeKey) 23 | 24 | return store.Has(types.RewardsKey(address)) 25 | } 26 | 27 | // SetRewards sets the given rewards 28 | func (k Keeper) SetRewards(ctx sdk.Context, rewards *types.Rewards) { 29 | store := ctx.KVStore(k.storeKey) 30 | 31 | bz := k.cdc.MustMarshal(rewards) 32 | 33 | store.Set(types.RewardsKey(rewards.Address), bz) 34 | } 35 | 36 | // AddDepositReward adds the deposit reward for the specified address by the given amount 37 | func (k Keeper) AddDepositReward(ctx sdk.Context, address string, amount sdk.Coin) { 38 | rewards := k.GetRewards(ctx, address) 39 | 40 | if len(rewards.Address) == 0 { 41 | rewards.Address = address 42 | rewards.DepositReward = amount 43 | rewards.TotalAmount = amount 44 | } else { 45 | rewards.DepositReward = amount.AddAmount(rewards.DepositReward.Amount) 46 | rewards.TotalAmount = amount.AddAmount(rewards.TotalAmount.Amount) 47 | } 48 | 49 | rewards.DepositCount += 1 50 | 51 | k.SetRewards(ctx, rewards) 52 | } 53 | 54 | // AddWithdrawReward adds the withdrawal reward for the specified address by the given amount 55 | func (k Keeper) AddWithdrawReward(ctx sdk.Context, address string, amount sdk.Coin) { 56 | rewards := k.GetRewards(ctx, address) 57 | 58 | if len(rewards.Address) == 0 { 59 | rewards.Address = address 60 | rewards.WithdrawReward = amount 61 | rewards.TotalAmount = amount 62 | } else { 63 | rewards.WithdrawReward = amount.AddAmount(rewards.WithdrawReward.Amount) 64 | rewards.TotalAmount = amount.AddAmount(rewards.TotalAmount.Amount) 65 | } 66 | 67 | rewards.WithdrawCount += 1 68 | 69 | k.SetRewards(ctx, rewards) 70 | } 71 | 72 | // GetRewardStats gets the reward statistics 73 | func (k Keeper) GetRewardStats(ctx sdk.Context) *types.RewardStats { 74 | store := ctx.KVStore(k.storeKey) 75 | 76 | var stats types.RewardStats 77 | bz := store.Get(types.RewardStatsKey) 78 | k.cdc.MustUnmarshal(bz, &stats) 79 | 80 | return &stats 81 | } 82 | 83 | // SetRewardStats sets the reward statistics 84 | func (k Keeper) SetRewardStats(ctx sdk.Context, rewardStats *types.RewardStats) { 85 | store := ctx.KVStore(k.storeKey) 86 | 87 | bz := k.cdc.MustMarshal(rewardStats) 88 | 89 | store.Set(types.RewardStatsKey, bz) 90 | } 91 | 92 | // UpdateRewardStats updates the reward statistics 93 | func (k Keeper) UpdateRewardStats(ctx sdk.Context, address string, reward sdk.Coin) { 94 | stats := k.GetRewardStats(ctx) 95 | 96 | if !k.HasRewards(ctx, address) { 97 | stats.AddressCount += 1 98 | } 99 | 100 | if stats.TxCount == 0 { 101 | stats.TotalRewardAmount = reward 102 | } else { 103 | stats.TotalRewardAmount = reward.AddAmount(stats.TotalRewardAmount.Amount) 104 | } 105 | 106 | stats.TxCount += 1 107 | 108 | k.SetRewardStats(ctx, stats) 109 | } 110 | 111 | // DistributeDepositReward distributes reward for deposit 112 | func (k Keeper) DistributeDepositReward(ctx sdk.Context, address string) error { 113 | if !k.DepositIncentiveEnabled(ctx) { 114 | return types.ErrDepositIncentiveNotEnabled 115 | } 116 | 117 | rewardAmount := k.RewardPerDeposit(ctx) 118 | 119 | if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.MustAccAddressFromBech32(address), sdk.NewCoins(rewardAmount)); err != nil { 120 | return err 121 | } 122 | 123 | k.AddDepositReward(ctx, address, rewardAmount) 124 | k.UpdateRewardStats(ctx, address, rewardAmount) 125 | 126 | return nil 127 | } 128 | 129 | // DistributeWithdrawReward distributes reward for withdrawal 130 | func (k Keeper) DistributeWithdrawReward(ctx sdk.Context, address string) error { 131 | if !k.WithdrawIncentiveEnabled(ctx) { 132 | return types.ErrWithdrawIncentiveNotEnabled 133 | } 134 | 135 | rewardAmount := k.RewardPerWithdraw(ctx) 136 | 137 | if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.MustAccAddressFromBech32(address), sdk.NewCoins(rewardAmount)); err != nil { 138 | return err 139 | } 140 | 141 | k.AddWithdrawReward(ctx, address, rewardAmount) 142 | k.UpdateRewardStats(ctx, address, rewardAmount) 143 | 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /x/btcbridge/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | errorsmod "cosmossdk.io/errors" 7 | ) 8 | 9 | // x/btcbridge module sentinel errors 10 | var ( 11 | ErrInvalidBlockHeader = errorsmod.Register(ModuleName, 1100, "invalid block header") 12 | ErrInvalidBlockHeaders = errorsmod.Register(ModuleName, 1101, "invalid block headers") 13 | ErrInvalidReorgDepth = errorsmod.Register(ModuleName, 1102, "invalid reorg depth") 14 | 15 | ErrBlockNotFound = errorsmod.Register(ModuleName, 2101, "block not found") 16 | ErrTransactionNotIncluded = errorsmod.Register(ModuleName, 2102, "transaction not included in block") 17 | ErrNotConfirmed = errorsmod.Register(ModuleName, 2103, "transaction not confirmed") 18 | ErrExceedMaxAcceptanceDepth = errorsmod.Register(ModuleName, 2104, "exceed max acceptance block depth") 19 | ErrUnsupportedScriptType = errorsmod.Register(ModuleName, 2105, "unsupported script type") 20 | ErrInvalidBtcTransaction = errorsmod.Register(ModuleName, 2106, "invalid bitcoin transaction") 21 | ErrTransactionAlreadyMinted = errorsmod.Register(ModuleName, 2107, "transaction already minted") 22 | ErrInvalidDepositTransaction = errorsmod.Register(ModuleName, 2108, "invalid deposit transaction") 23 | ErrInvalidDepositAmount = errorsmod.Register(ModuleName, 2109, "invalid deposit amount") 24 | ErrDepositNotEnabled = errorsmod.Register(ModuleName, 2110, "deposit not enabled") 25 | ErrUntrustedBtcRelayer = errorsmod.Register(ModuleName, 2111, "untrusted btc relayer") 26 | ErrUntrustedNonBtcRelayer = errorsmod.Register(ModuleName, 2112, "untrusted non btc relayer") 27 | ErrUntrustedFeeProvider = errorsmod.Register(ModuleName, 2113, "untrusted fee provider") 28 | 29 | ErrInvalidWithdrawAmount = errorsmod.Register(ModuleName, 3100, "invalid withdrawal amount") 30 | ErrInvalidBtcAddress = errorsmod.Register(ModuleName, 3101, "invalid btc address") 31 | ErrAssetNotSupported = errorsmod.Register(ModuleName, 3102, "asset not supported") 32 | ErrInvalidFeeRate = errorsmod.Register(ModuleName, 3103, "invalid fee rate") 33 | ErrDustOutput = errorsmod.Register(ModuleName, 3104, "too small output amount") 34 | ErrInsufficientUTXOs = errorsmod.Register(ModuleName, 3105, "insufficient utxos") 35 | ErrMaxTransactionWeightExceeded = errorsmod.Register(ModuleName, 3106, "maximum transaction weight exceeded") 36 | ErrMaxUTXONumExceeded = errorsmod.Register(ModuleName, 3107, "maximum utxo number exceeded") 37 | ErrFailToSerializePsbt = errorsmod.Register(ModuleName, 3108, "failed to serialize psbt") 38 | ErrInvalidSignatures = errorsmod.Register(ModuleName, 3109, "invalid signatures") 39 | ErrSigningRequestDoesNotExist = errorsmod.Register(ModuleName, 3110, "signing request does not exist") 40 | ErrSigningRequestConfirmed = errorsmod.Register(ModuleName, 3111, "signing request has been confirmed") 41 | ErrWithdrawNotEnabled = errorsmod.Register(ModuleName, 3112, "withdrawal not enabled") 42 | 43 | ErrUTXODoesNotExist = errorsmod.Register(ModuleName, 4100, "utxo does not exist") 44 | ErrUTXOLocked = errorsmod.Register(ModuleName, 4101, "utxo locked") 45 | ErrUTXOUnlocked = errorsmod.Register(ModuleName, 4102, "utxo unlocked") 46 | 47 | ErrInvalidRunes = errorsmod.Register(ModuleName, 5100, "invalid runes") 48 | ErrInvalidRuneId = errorsmod.Register(ModuleName, 5101, "invalid rune id") 49 | 50 | ErrInvalidParams = errorsmod.Register(ModuleName, 6100, "invalid module params") 51 | ErrInvalidRelayers = errorsmod.Register(ModuleName, 6101, "invalid relayers") 52 | ErrInvalidFeeProviders = errorsmod.Register(ModuleName, 6102, "invalid fee providers") 53 | 54 | ErrInvalidDKGParams = errorsmod.Register(ModuleName, 7100, "invalid dkg params") 55 | ErrDKGRequestDoesNotExist = errorsmod.Register(ModuleName, 7101, "dkg request does not exist") 56 | ErrDKGCompletionRequestExists = errorsmod.Register(ModuleName, 7102, "dkg completion request already exists") 57 | ErrInvalidDKGCompletionRequest = errorsmod.Register(ModuleName, 7103, "invalid dkg completion request") 58 | ErrUnauthorizedDKGCompletionRequest = errorsmod.Register(ModuleName, 7104, "unauthorized dkg completion request") 59 | ErrInvalidVaultVersion = errorsmod.Register(ModuleName, 7105, "invalid vault version") 60 | ErrInvalidVault = errorsmod.Register(ModuleName, 7106, "invalid vault") 61 | ErrVaultDoesNotExist = errorsmod.Register(ModuleName, 7107, "vault does not exist") 62 | ErrInvalidPsbt = errorsmod.Register(ModuleName, 7108, "invalid psbt") 63 | 64 | ErrInvalidConsolidation = errorsmod.Register(ModuleName, 8100, "invalid consolidation") 65 | ) 66 | -------------------------------------------------------------------------------- /cmd/sided/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/pflag" 8 | 9 | "cosmossdk.io/log" 10 | dbm "github.com/cosmos/cosmos-db" 11 | "github.com/cosmos/cosmos-sdk/client" 12 | "github.com/cosmos/cosmos-sdk/client/config" 13 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 14 | "github.com/cosmos/cosmos-sdk/server" 15 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 16 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 17 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 18 | txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 19 | "github.com/cosmos/cosmos-sdk/x/auth/types" 20 | 21 | // this line is used by starport scaffolding # root/moduleImport 22 | 23 | "github.com/sideprotocol/side/app" 24 | "github.com/sideprotocol/side/app/params" 25 | ) 26 | 27 | // NewRootCmd creates a new root command for a Cosmos SDK application 28 | func NewRootCmd() *cobra.Command { 29 | // we "pre"-instantiate the application for getting the injected/configured encoding configuration 30 | // note, this is not necessary when using app wiring, as depinject can be directly used (see root_v2.go) 31 | tempApp := app.New(log.NewNopLogger(), dbm.NewMemDB(), nil, false, simtestutil.NewAppOptionsWithFlagHome(tempDir())) 32 | encodingConfig := params.EncodingConfig{ 33 | InterfaceRegistry: tempApp.InterfaceRegistry(), 34 | Codec: tempApp.AppCodec(), 35 | TxConfig: tempApp.TxConfig(), 36 | Amino: tempApp.LegacyAmino(), 37 | } 38 | 39 | initClientCtx := client.Context{}. 40 | WithCodec(encodingConfig.Codec). 41 | WithInterfaceRegistry(encodingConfig.InterfaceRegistry). 42 | WithTxConfig(encodingConfig.TxConfig). 43 | WithLegacyAmino(encodingConfig.Amino). 44 | WithInput(os.Stdin). 45 | WithAccountRetriever(types.AccountRetriever{}). 46 | WithHomeDir(app.DefaultNodeHome). 47 | WithViper("") 48 | 49 | rootCmd := &cobra.Command{ 50 | Use: app.Name + "d", 51 | Short: "Start side node", 52 | SilenceErrors: true, 53 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { 54 | // set the default command outputs 55 | cmd.SetOut(cmd.OutOrStdout()) 56 | cmd.SetErr(cmd.ErrOrStderr()) 57 | 58 | initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) 59 | initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | initClientCtx, err = config.ReadFromClientConfig(initClientCtx) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // This needs to go after ReadFromClientConfig, as that function 70 | // sets the RPC client needed for SIGN_MODE_TEXTUAL. This sign mode 71 | // is only available if the client is online. 72 | if !initClientCtx.Offline { 73 | enabledSignModes := append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) 74 | txConfigOpts := tx.ConfigOptions{ 75 | EnabledSignModes: enabledSignModes, 76 | TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), 77 | } 78 | txConfig, err := tx.NewTxConfigWithOptions( 79 | initClientCtx.Codec, 80 | txConfigOpts, 81 | ) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | initClientCtx = initClientCtx.WithTxConfig(txConfig) 87 | } 88 | 89 | if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { 90 | return err 91 | } 92 | 93 | customAppTemplate, customAppConfig := initAppConfig() 94 | customCMTConfig := initCometBFTConfig() 95 | 96 | return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) 97 | }, 98 | } 99 | 100 | initRootCmd(rootCmd, encodingConfig.TxConfig, tempApp.BasicModuleManager) 101 | 102 | // add keyring to autocli opts 103 | autoCliOpts := tempApp.AutoCliOpts() 104 | initClientCtx, _ = config.ReadFromClientConfig(initClientCtx) 105 | autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) 106 | autoCliOpts.ClientCtx = initClientCtx 107 | autoCliOpts.TxConfigOpts = tx.ConfigOptions{ 108 | EnabledSignModes: tx.DefaultSignModes, 109 | TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), 110 | } 111 | 112 | if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { 113 | panic(err) 114 | } 115 | 116 | return rootCmd 117 | } 118 | 119 | func overwriteFlagDefaults(c *cobra.Command, defaults map[string]string) { 120 | set := func(s *pflag.FlagSet, key, val string) { 121 | if f := s.Lookup(key); f != nil { 122 | f.DefValue = val 123 | _ = f.Value.Set(val) 124 | } 125 | } 126 | for key, val := range defaults { 127 | set(c.Flags(), key, val) 128 | set(c.PersistentFlags(), key, val) 129 | } 130 | for _, c := range c.Commands() { 131 | overwriteFlagDefaults(c, defaults) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /x/btcbridge/types/policy.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/btcutil" 5 | "github.com/btcsuite/btcd/chaincfg" 6 | "github.com/btcsuite/btcd/txscript" 7 | "github.com/btcsuite/btcd/wire" 8 | ) 9 | 10 | const ( 11 | // maximum allowed number of the non-vault outputs for the btc deposit transaction 12 | MaxNonVaultOutNum = 1 13 | 14 | // maximum allowed number of the non-vault outputs for the runes deposit transaction 15 | RunesMaxNonVaultOutNum = 3 16 | 17 | // allowed number of edicts in the runes payload for the runes deposit transaction 18 | RunesEdictNum = 1 19 | 20 | // transaction input sequence intended to identify the txs built by the bridge (for relayers' convenience) 21 | MagicSequence = 1<<31 + 0xde 22 | ) 23 | 24 | // ExtractRecipientAddr extracts the recipient address for minting voucher token by the type of the asset to be deposited 25 | func ExtractRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, isRunes bool, chainCfg *chaincfg.Params) (btcutil.Address, error) { 26 | if isRunes { 27 | return ExtractRunesRecipientAddr(tx, prevTx, vaults, chainCfg) 28 | } 29 | 30 | return ExtractCommonRecipientAddr(tx, prevTx, vaults, chainCfg) 31 | } 32 | 33 | // ExtractCommonRecipientAddr extracts the recipient address for minting voucher token in the common case. 34 | // First, extract the recipient from the tx out which is a non-vault address; 35 | // Then fall back to the first input 36 | func ExtractCommonRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, chainCfg *chaincfg.Params) (btcutil.Address, error) { 37 | var recipient btcutil.Address 38 | 39 | nonVaultOutCount := 0 40 | 41 | // extract from the tx out which is a non-vault address 42 | for _, out := range tx.TxOut { 43 | pkScript, err := txscript.ParsePkScript(out.PkScript) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | addr, err := pkScript.Address(chainCfg) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | vault := SelectVaultByAddress(vaults, addr.EncodeAddress()) 54 | if vault == nil { 55 | recipient = addr 56 | nonVaultOutCount++ 57 | } 58 | } 59 | 60 | // exceed allowed non vault out number 61 | if nonVaultOutCount > MaxNonVaultOutNum { 62 | return nil, ErrInvalidDepositTransaction 63 | } 64 | 65 | if recipient != nil { 66 | return recipient, nil 67 | } 68 | 69 | // fall back to extract from the first input 70 | pkScript, err := txscript.ParsePkScript(prevTx.TxOut[tx.TxIn[0].PreviousOutPoint.Index].PkScript) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return pkScript.Address(chainCfg) 76 | } 77 | 78 | // ExtractRunesRecipientAddr extracts the recipient address for minting runes voucher token. 79 | // First, extract the recipient from the tx out which is a non-vault and non-OP_RETURN output; 80 | // Then fall back to the first input 81 | func ExtractRunesRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, chainCfg *chaincfg.Params) (btcutil.Address, error) { 82 | var recipient btcutil.Address 83 | 84 | nonVaultOutCount := 0 85 | 86 | // extract from the tx out which is a non-vault and non-OP_RETURN output 87 | for _, out := range tx.TxOut { 88 | if IsOpReturnOutput(out) { 89 | nonVaultOutCount++ 90 | continue 91 | } 92 | 93 | pkScript, err := txscript.ParsePkScript(out.PkScript) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | addr, err := pkScript.Address(chainCfg) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | vault := SelectVaultByAddress(vaults, addr.EncodeAddress()) 104 | if vault == nil { 105 | recipient = addr 106 | nonVaultOutCount++ 107 | } 108 | } 109 | 110 | // exceed allowed non vault out number 111 | if nonVaultOutCount > RunesMaxNonVaultOutNum { 112 | return nil, ErrInvalidDepositTransaction 113 | } 114 | 115 | if recipient != nil { 116 | return recipient, nil 117 | } 118 | 119 | // fall back to extract from the first input 120 | pkScript, err := txscript.ParsePkScript(prevTx.TxOut[tx.TxIn[0].PreviousOutPoint.Index].PkScript) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return pkScript.Address(chainCfg) 126 | } 127 | 128 | // CheckRunesDepositTransaction checks if the given tx is valid runes deposit tx 129 | func CheckRunesDepositTransaction(tx *wire.MsgTx, vaults []*Vault) (*Edict, error) { 130 | edicts, err := ParseRunes(tx) 131 | if err != nil { 132 | return nil, ErrInvalidDepositTransaction 133 | } 134 | 135 | if len(edicts) == 0 { 136 | return nil, nil 137 | } 138 | 139 | if len(edicts) != RunesEdictNum { 140 | return nil, ErrInvalidDepositTransaction 141 | } 142 | 143 | // even split is not supported 144 | if edicts[0].Output == uint32(len(tx.TxOut)) { 145 | return nil, ErrInvalidDepositTransaction 146 | } 147 | 148 | vault := SelectVaultByPkScript(vaults, tx.TxOut[edicts[0].Output].PkScript) 149 | if vault == nil || vault.AssetType != AssetType_ASSET_TYPE_RUNES { 150 | return nil, ErrInvalidDepositTransaction 151 | } 152 | 153 | return edicts[0], nil 154 | } 155 | -------------------------------------------------------------------------------- /x/btcbridge/keeper/consolidate.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | "github.com/sideprotocol/side/x/btcbridge/types" 9 | ) 10 | 11 | // ConsolidateVaults performs the UTXO consolidation for the given vaults 12 | func (k Keeper) ConsolidateVaults(ctx sdk.Context, vaultVersion uint64, btcConsolidation *types.BtcConsolidation, runesConsolidations []*types.RunesConsolidation) error { 13 | feeRate := k.GetFeeRate(ctx) 14 | if err := k.CheckFeeRate(ctx, feeRate); err != nil { 15 | return err 16 | } 17 | 18 | if btcConsolidation != nil { 19 | if err := k.handleBtcConsolidation(ctx, vaultVersion, btcConsolidation.TargetThreshold, btcConsolidation.MaxNum, feeRate.Value); err != nil { 20 | return err 21 | } 22 | } 23 | 24 | for _, rc := range runesConsolidations { 25 | if err := k.handleRunesConsolidation(ctx, vaultVersion, rc.RuneId, rc.TargetThreshold, rc.MaxNum, feeRate.Value); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | // handleBtcConsolidation handles the given btc consolidation 34 | func (k Keeper) handleBtcConsolidation(ctx sdk.Context, vaultVersion uint64, targetThreshold int64, maxNum uint32, feeRate int64) error { 35 | vault := k.GetVaultByAssetTypeAndVersion(ctx, types.AssetType_ASSET_TYPE_BTC, vaultVersion) 36 | if vault == nil { 37 | return types.ErrVaultDoesNotExist 38 | } 39 | 40 | maxUtxoNum := uint32(k.GetMaxUtxoNum(ctx)) 41 | if maxNum > maxUtxoNum { 42 | maxNum = maxUtxoNum 43 | } 44 | 45 | targetUTXOs := k.GetUnlockedUTXOsByAddrAndThreshold(ctx, vault.Address, targetThreshold, maxNum) 46 | if len(targetUTXOs) == 0 { 47 | return types.ErrInsufficientUTXOs 48 | } 49 | 50 | p, recipientUTXO, err := types.BuildTransferAllBtcPsbt(targetUTXOs, vault.Address, feeRate) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | psbtB64, err := p.B64Encode() 56 | if err != nil { 57 | return types.ErrFailToSerializePsbt 58 | } 59 | 60 | txHash := p.UnsignedTx.TxHash().String() 61 | 62 | // spend the involved utxos 63 | _ = k.SpendUTXOs(ctx, targetUTXOs) 64 | 65 | // lock the recipient(change) utxo 66 | k.lockChangeUTXOs(ctx, txHash, recipientUTXO) 67 | 68 | // set signing request 69 | signingReq := &types.SigningRequest{ 70 | Address: k.authority, 71 | Sequence: k.IncrementSigningRequestSequence(ctx), 72 | Type: types.AssetType_ASSET_TYPE_BTC, 73 | Txid: txHash, 74 | Psbt: psbtB64, 75 | CreationTime: ctx.BlockTime(), 76 | Status: types.SigningStatus_SIGNING_STATUS_PENDING, 77 | } 78 | k.SetSigningRequest(ctx, signingReq) 79 | 80 | // Emit events 81 | k.EmitEvent(ctx, k.authority, 82 | sdk.NewAttribute("sequence", fmt.Sprintf("%d", signingReq.Sequence)), 83 | sdk.NewAttribute("txid", signingReq.Txid), 84 | ) 85 | 86 | return nil 87 | } 88 | 89 | // handleRunesConsolidation handles the given runes consolidation 90 | func (k Keeper) handleRunesConsolidation(ctx sdk.Context, vaultVersion uint64, runeId string, targetThreshold string, maxNum uint32, feeRate int64) error { 91 | vault := k.GetVaultByAssetTypeAndVersion(ctx, types.AssetType_ASSET_TYPE_RUNES, vaultVersion) 92 | if vault == nil { 93 | return types.ErrVaultDoesNotExist 94 | } 95 | 96 | btcVault := k.GetVaultByAssetTypeAndVersion(ctx, types.AssetType_ASSET_TYPE_BTC, vaultVersion) 97 | if btcVault == nil { 98 | return types.ErrVaultDoesNotExist 99 | } 100 | 101 | maxUtxoNum := uint32(k.GetMaxUtxoNum(ctx)) 102 | if maxNum > maxUtxoNum { 103 | maxNum = maxUtxoNum 104 | } 105 | 106 | targetRunesUTXOs, runeBalances := k.GetTargetRunesUTXOsByAddrAndThreshold(ctx, vault.Address, runeId, types.RuneAmountFromString(targetThreshold), maxNum) 107 | if len(targetRunesUTXOs) == 0 { 108 | return types.ErrInsufficientUTXOs 109 | } 110 | 111 | btcUtxoIterator := k.GetUTXOIteratorByAddr(ctx, btcVault.Address) 112 | 113 | p, selectedUtxos, changeUtxo, runesRecipientUtxo, err := types.BuildTransferAllRunesPsbt(targetRunesUTXOs, btcUtxoIterator, vault.Address, runeBalances, feeRate, btcVault.Address, k.GetMaxUtxoNum(ctx)) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | psbtB64, err := p.B64Encode() 119 | if err != nil { 120 | return types.ErrFailToSerializePsbt 121 | } 122 | 123 | txHash := p.UnsignedTx.TxHash().String() 124 | 125 | // spend the involved utxos 126 | _ = k.SpendUTXOs(ctx, targetRunesUTXOs) 127 | _ = k.SpendUTXOs(ctx, selectedUtxos) 128 | 129 | // lock the change utxos 130 | k.lockChangeUTXOs(ctx, txHash, changeUtxo, runesRecipientUtxo) 131 | 132 | // set signing request 133 | signingReq := &types.SigningRequest{ 134 | Address: k.authority, 135 | Sequence: k.IncrementSigningRequestSequence(ctx), 136 | Type: types.AssetType_ASSET_TYPE_RUNES, 137 | Txid: txHash, 138 | Psbt: psbtB64, 139 | CreationTime: ctx.BlockTime(), 140 | Status: types.SigningStatus_SIGNING_STATUS_PENDING, 141 | } 142 | k.SetSigningRequest(ctx, signingReq) 143 | 144 | // Emit events 145 | k.EmitEvent(ctx, k.authority, 146 | sdk.NewAttribute("sequence", fmt.Sprintf("%d", signingReq.Sequence)), 147 | sdk.NewAttribute("txid", signingReq.Txid), 148 | ) 149 | 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /x/btcbridge/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | const ( 8 | // ModuleName defines the module name 9 | ModuleName = "btcbridge" 10 | 11 | // StoreKey defines the primary module store key 12 | StoreKey = ModuleName 13 | 14 | // RouterKey defines the module's message routing key 15 | RouterKey = ModuleName 16 | 17 | // MemStoreKey defines the in-memory store key 18 | MemStoreKey = "mem_btcbridge" 19 | ) 20 | 21 | var ( 22 | ParamsStoreKey = []byte{0x1} 23 | 24 | BtcBlockHeaderHashPrefix = []byte{0x10} // prefix for each key to a block header, for a hash 25 | BtcBlockHeaderHeightPrefix = []byte{0x11} // prefix for each key to a block hash, for a height 26 | BtcBestBlockHeaderKey = []byte{0x12} // key for the best block height 27 | BtcFeeRateKey = []byte{0x13} // key for the bitcoin network fee rate 28 | 29 | BtcWithdrawRequestSequenceKey = []byte{0x20} // key for the withdrawal request sequence 30 | BtcWithdrawRequestKeyPrefix = []byte{0x21} // prefix for each key to a withdrawal request 31 | BtcWithdrawRequestByTxHashKeyPrefix = []byte{0x22} // prefix for each key to a withdrawal request by tx hash 32 | BtcWithdrawRequestQueueKeyPrefix = []byte{0x23} // prefix for each key to a pending btc withdrawal request 33 | BtcSigningRequestSequenceKey = []byte{0x24} // key for the signing request sequence 34 | BtcSigningRequestPrefix = []byte{0x25} // prefix for each key to a signing request 35 | BtcSigningRequestByTxHashPrefix = []byte{0x26} // prefix for each key to a signing request from tx hash 36 | BtcSigningRequestByStatusKeyPrefix = []byte{0x27} // prefix for each key to a signing request by status 37 | BtcMintedTxHashKeyPrefix = []byte{0x28} // prefix for each key to a minted tx hash 38 | 39 | BtcUtxoKeyPrefix = []byte{0x30} // prefix for each key to a utxo 40 | BtcOwnerUtxoKeyPrefix = []byte{0x31} // prefix for each key to an owned utxo 41 | BtcOwnerUtxoByAmountKeyPrefix = []byte{0x32} // prefix for each key to an owned utxo by amount 42 | BtcOwnerRunesUtxoKeyPrefix = []byte{0x33} // prefix for each key to an owned runes utxo 43 | 44 | DKGRequestIDKey = []byte{0x40} // key for the DKG request id 45 | DKGRequestKeyPrefix = []byte{0x41} // prefix for each key to a DKG request 46 | DKGCompletionRequestKeyPrefix = []byte{0x42} // prefix for each key to a DKG completion request 47 | VaultVersionKey = []byte{0x43} // key for vault version increased by 1 once updated 48 | ) 49 | 50 | func BtcBlockHeaderHashKey(hash string) []byte { 51 | return append(BtcBlockHeaderHashPrefix, []byte(hash)...) 52 | } 53 | 54 | func BtcBlockHeaderHeightKey(height uint64) []byte { 55 | return append(BtcBlockHeaderHeightPrefix, sdk.Uint64ToBigEndian(height)...) 56 | } 57 | 58 | func BtcWithdrawRequestKey(sequence uint64) []byte { 59 | return append(BtcWithdrawRequestKeyPrefix, sdk.Uint64ToBigEndian(sequence)...) 60 | } 61 | 62 | func BtcWithdrawRequestByTxHashKey(txid string, sequence uint64) []byte { 63 | return append(append(BtcWithdrawRequestByTxHashKeyPrefix, []byte(txid)...), sdk.Uint64ToBigEndian(sequence)...) 64 | } 65 | 66 | func BtcWithdrawRequestQueueKey(sequence uint64) []byte { 67 | return append(BtcWithdrawRequestQueueKeyPrefix, sdk.Uint64ToBigEndian(sequence)...) 68 | } 69 | 70 | func BtcSigningRequestKey(sequence uint64) []byte { 71 | return append(BtcSigningRequestPrefix, sdk.Uint64ToBigEndian(sequence)...) 72 | } 73 | 74 | func BtcSigningRequestByTxHashKey(txid string) []byte { 75 | return append(BtcSigningRequestByTxHashPrefix, []byte(txid)...) 76 | } 77 | 78 | func BtcSigningRequestByStatusKey(status SigningStatus, sequence uint64) []byte { 79 | key := append(BtcSigningRequestByStatusKeyPrefix, sdk.Uint64ToBigEndian(uint64(status))...) 80 | 81 | return append(key, sdk.Uint64ToBigEndian(sequence)...) 82 | } 83 | 84 | func BtcMintedTxHashKey(hash string) []byte { 85 | return append(BtcMintedTxHashKeyPrefix, []byte(hash)...) 86 | } 87 | 88 | func BtcUtxoKey(hash string, vout uint64) []byte { 89 | return append(append(BtcUtxoKeyPrefix, []byte(hash)...), sdk.Uint64ToBigEndian(vout)...) 90 | } 91 | 92 | func BtcOwnerUtxoKey(owner string, hash string, vout uint64) []byte { 93 | key := append(append(BtcOwnerUtxoKeyPrefix, []byte(owner)...), []byte(hash)...) 94 | key = append(key, sdk.Uint64ToBigEndian(vout)...) 95 | 96 | return key 97 | } 98 | 99 | func BtcOwnerUtxoByAmountKey(owner string, amount uint64, hash string, vout uint64) []byte { 100 | key := append(append(BtcOwnerUtxoByAmountKeyPrefix, []byte(owner)...), sdk.Uint64ToBigEndian(amount)...) 101 | key = append(key, []byte(hash)...) 102 | key = append(key, sdk.Uint64ToBigEndian(vout)...) 103 | 104 | return key 105 | } 106 | 107 | func BtcOwnerRunesUtxoKey(owner string, id string, amount string, hash string, vout uint64) []byte { 108 | key := append(append(BtcOwnerRunesUtxoKeyPrefix, []byte(owner)...), MarshalRuneIdFromString(id)...) 109 | key = append(key, MarshalRuneAmountFromString(amount)...) 110 | key = append(key, []byte(hash)...) 111 | key = append(key, sdk.Uint64ToBigEndian(vout)...) 112 | 113 | return key 114 | } 115 | 116 | func DKGRequestKey(id uint64) []byte { 117 | return append(DKGRequestKeyPrefix, sdk.Uint64ToBigEndian(id)...) 118 | } 119 | 120 | func DKGCompletionRequestKey(id uint64, consAddress string) []byte { 121 | return append(append(DKGCompletionRequestKeyPrefix, sdk.Uint64ToBigEndian(id)...), []byte(consAddress)...) 122 | } 123 | -------------------------------------------------------------------------------- /x/btcbridge/module/abci.go: -------------------------------------------------------------------------------- 1 | package btcbridge 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | "github.com/sideprotocol/side/x/btcbridge/keeper" 9 | "github.com/sideprotocol/side/x/btcbridge/types" 10 | ) 11 | 12 | // EndBlocker called at every block 13 | func EndBlocker(ctx sdk.Context, k keeper.Keeper) { 14 | handleBtcWithdrawRequests(ctx, k) 15 | handleDKGRequests(ctx, k) 16 | handleVaultTransfer(ctx, k) 17 | } 18 | 19 | // handleBtcWithdrawRequests performs the batch btc withdrawal request handling 20 | func handleBtcWithdrawRequests(ctx sdk.Context, k keeper.Keeper) { 21 | p := k.GetParams(ctx) 22 | 23 | // check if withdrawal is enabled 24 | if !p.WithdrawEnabled { 25 | return 26 | } 27 | 28 | // check block height 29 | if ctx.BlockHeight()%p.WithdrawParams.BtcBatchWithdrawPeriod != 0 { 30 | return 31 | } 32 | 33 | // get the pending btc withdrawal request 34 | pendingWithdrawRequests := k.GetPendingBtcWithdrawRequests(ctx, p.WithdrawParams.MaxBtcBatchWithdrawNum) 35 | if len(pendingWithdrawRequests) == 0 { 36 | return 37 | } 38 | 39 | feeRate := k.GetFeeRate(ctx) 40 | if err := k.CheckFeeRate(ctx, feeRate); err != nil { 41 | k.Logger(ctx).Info("invalid fee rate", "value", feeRate.Value, "height", feeRate.Height) 42 | return 43 | } 44 | 45 | vault := types.SelectVaultByAssetType(p.Vaults, types.AssetType_ASSET_TYPE_BTC) 46 | if vault == nil { 47 | k.Logger(ctx).Info("btc vault does not exist") 48 | return 49 | } 50 | 51 | signingRequest, err := k.BuildBtcBatchWithdrawSigningRequest(ctx, pendingWithdrawRequests, feeRate.Value, vault.Address) 52 | if err != nil { 53 | k.Logger(ctx).Info("failed to build signing request", "err", err) 54 | return 55 | } 56 | 57 | for _, req := range pendingWithdrawRequests { 58 | // update withdrawal request 59 | req.Txid = signingRequest.Txid 60 | k.SetWithdrawRequest(ctx, req) 61 | 62 | // remove from the pending queue 63 | k.RemoveFromBtcWithdrawRequestQueue(ctx, req) 64 | 65 | // emit event 66 | k.EmitEvent(ctx, req.Address, 67 | sdk.NewAttribute("sequence", fmt.Sprintf("%d", req.Sequence)), 68 | sdk.NewAttribute("txid", req.Txid), 69 | ) 70 | } 71 | } 72 | 73 | // handleDKGRequests performs the DKG request handling 74 | func handleDKGRequests(ctx sdk.Context, k keeper.Keeper) { 75 | pendingDKGRequests := k.GetPendingDKGRequests(ctx) 76 | 77 | for _, req := range pendingDKGRequests { 78 | // check if the DKG request expired 79 | if !ctx.BlockTime().Before(*req.Expiration) { 80 | req.Status = types.DKGRequestStatus_DKG_REQUEST_STATUS_TIMEDOUT 81 | k.SetDKGRequest(ctx, req) 82 | 83 | continue 84 | } 85 | 86 | // handle DKG completion requests 87 | completionRequests := k.GetDKGCompletionRequests(ctx, req.Id) 88 | if len(completionRequests) != len(req.Participants) { 89 | continue 90 | } 91 | 92 | // check if the DKG completion requests are valid 93 | if !types.CheckDKGCompletionRequests(completionRequests) { 94 | req.Status = types.DKGRequestStatus_DKG_REQUEST_STATUS_FAILED 95 | k.SetDKGRequest(ctx, req) 96 | 97 | continue 98 | } 99 | 100 | // update vaults 101 | k.UpdateVaults(ctx, completionRequests[0].Vaults, req.VaultTypes) 102 | 103 | // update status 104 | req.Status = types.DKGRequestStatus_DKG_REQUEST_STATUS_COMPLETED 105 | k.SetDKGRequest(ctx, req) 106 | } 107 | } 108 | 109 | // handleVaultTransfer performs the vault asset transfer 110 | func handleVaultTransfer(ctx sdk.Context, k keeper.Keeper) { 111 | completedDKGRequests := k.GetDKGRequests(ctx, types.DKGRequestStatus_DKG_REQUEST_STATUS_COMPLETED) 112 | 113 | for _, req := range completedDKGRequests { 114 | if req.EnableTransfer { 115 | completions := k.GetDKGCompletionRequests(ctx, req.Id) 116 | dkgVaultVersion, _ := k.GetVaultVersionByAddress(ctx, completions[0].Vaults[0]) 117 | 118 | sourceVersion := dkgVaultVersion - 1 119 | destVersion := k.GetLatestVaultVersion(ctx) 120 | 121 | if k.VaultsTransferCompleted(ctx, sourceVersion) { 122 | continue 123 | } 124 | 125 | sourceBtcVault := k.GetVaultByAssetTypeAndVersion(ctx, types.AssetType_ASSET_TYPE_BTC, sourceVersion).Address 126 | sourceRunesVault := k.GetVaultByAssetTypeAndVersion(ctx, types.AssetType_ASSET_TYPE_RUNES, sourceVersion).Address 127 | 128 | // transfer runes 129 | if !k.VaultTransferCompleted(ctx, sourceRunesVault) { 130 | if err := k.TransferVault(ctx, sourceVersion, destVersion, types.AssetType_ASSET_TYPE_RUNES, nil, req.TargetUtxoNum); err != nil { 131 | k.Logger(ctx).Info("failed to transfer vault", "source version", sourceVersion, "destination version", destVersion, "asset type", types.AssetType_ASSET_TYPE_RUNES, "target utxo num", req.TargetUtxoNum, "err", err) 132 | continue 133 | } 134 | } 135 | 136 | // transfer btc only when runes transfer completed 137 | if k.VaultTransferCompleted(ctx, sourceRunesVault) && !k.VaultTransferCompleted(ctx, sourceBtcVault) { 138 | if err := k.TransferVault(ctx, sourceVersion, destVersion, types.AssetType_ASSET_TYPE_BTC, nil, req.TargetUtxoNum); err != nil { 139 | k.Logger(ctx).Info("failed to transfer vault", "source version", sourceVersion, "destination version", destVersion, "asset type", types.AssetType_ASSET_TYPE_BTC, "target utxo num", req.TargetUtxoNum, "err", err) 140 | continue 141 | } 142 | } 143 | 144 | if k.VaultsTransferCompleted(ctx, sourceVersion) { 145 | k.Logger(ctx).Info("vaults transfer completed", "source version", sourceVersion, "destination version", destVersion) 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /proto/side/btcbridge/btcbridge.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package side.btcbridge; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/protobuf/any.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | import "cosmos_proto/cosmos.proto"; 9 | import "side/btcbridge/params.proto"; 10 | 11 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types"; 12 | 13 | // Bitcoin Block Header 14 | message BlockHeader { 15 | uint64 version = 1; 16 | string hash = 2; 17 | uint64 height = 3; 18 | string previous_block_hash = 4; 19 | string merkle_root = 5; 20 | uint64 nonce = 6; 21 | string bits = 7; 22 | uint64 time = 8; 23 | uint64 ntx = 9; 24 | } 25 | 26 | // Fee rate 27 | message FeeRate { 28 | // fee rate 29 | int64 value = 1; 30 | // block height at which the fee rate is submitted 31 | int64 height = 2; 32 | } 33 | 34 | // Bitcoin Signing Status 35 | enum SigningStatus { 36 | // SIGNING_STATUS_UNSPECIFIED - Default value, should not be used 37 | SIGNING_STATUS_UNSPECIFIED = 0; 38 | // SIGNING_STATUS_PENDING - The signing request is pending 39 | SIGNING_STATUS_PENDING = 1; 40 | // SIGNING_STATUS_BROADCASTED - The signing request is broadcasted 41 | SIGNING_STATUS_BROADCASTED = 2; 42 | // SIGNING_STATUS_CONFIRMED - The signing request is confirmed 43 | SIGNING_STATUS_CONFIRMED = 3; 44 | // SIGNING_STATUS_FAILED - The signing request failed to be signed or broadcast due to unexpected exceptions 45 | SIGNING_STATUS_FAILED = 4; 46 | } 47 | 48 | // Bitcoin Signing Request 49 | message SigningRequest { 50 | string address = 1; 51 | uint64 sequence = 2; 52 | AssetType type = 3; 53 | string txid = 4; 54 | string psbt = 5; 55 | google.protobuf.Timestamp creation_time = 6 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 56 | SigningStatus status = 7; 57 | } 58 | 59 | // Withdrawal Request 60 | message WithdrawRequest { 61 | string address = 1; 62 | string amount = 2; 63 | uint64 sequence = 3; 64 | string txid = 4; 65 | } 66 | 67 | // Bitcoin UTXO 68 | message UTXO { 69 | string txid = 1; 70 | uint64 vout = 2; 71 | string address = 3; 72 | uint64 amount = 4; 73 | uint64 height = 5; 74 | bytes pub_key_script = 6; 75 | bool is_locked = 7; 76 | // rune balances associated with the UTXO 77 | repeated RuneBalance runes = 8; 78 | } 79 | 80 | // Rune Balance 81 | message RuneBalance { 82 | // serialized rune id 83 | string id = 1; 84 | // rune amount 85 | string amount = 2; 86 | } 87 | 88 | // Rune ID 89 | message RuneId { 90 | // block height 91 | uint64 block = 1; 92 | // tx index 93 | uint32 tx = 2; 94 | } 95 | 96 | // Rune Edict 97 | message Edict { 98 | RuneId id = 1; 99 | string amount = 2; 100 | uint32 output = 3; 101 | } 102 | 103 | // BTC UTXO Consolidation 104 | message BtcConsolidation { 105 | // maximum threshold of the btc value 106 | int64 target_threshold = 1; 107 | // maximum number of the utxos to be consolidated; 0 means all 108 | uint32 max_num = 2; 109 | } 110 | 111 | // Runes UTXO Consolidation 112 | message RunesConsolidation { 113 | // rune id 114 | string rune_id = 1; 115 | // maximum threshold of the corresponding rune balance 116 | string target_threshold = 2; 117 | // maximum number of the utxos to be consolidated; 0 means all 118 | uint32 max_num = 3; 119 | } 120 | 121 | // DKG Participant 122 | message DKGParticipant { 123 | // the moniker of the corresponding validator 124 | string moniker = 1; 125 | // the operator address of the corresponding validator 126 | string operator_address = 2; 127 | // the consensus public key of the corresponding validator 128 | string consensus_pubkey = 3; 129 | } 130 | 131 | enum DKGRequestStatus { 132 | // DKG_REQUEST_STATUS_UNSPECIFIED defines the unknown DKG request status 133 | DKG_REQUEST_STATUS_UNSPECIFIED = 0; 134 | // DKG_REQUEST_STATUS_PENDING defines the status of the DKG request which is pending 135 | DKG_REQUEST_STATUS_PENDING = 1; 136 | // DKG_REQUEST_STATUS_COMPLETED defines the status of the DKG request which is completed 137 | DKG_REQUEST_STATUS_COMPLETED = 2; 138 | // DKG_REQUEST_STATUS_FAILED defines the status of the DKG request which failed 139 | DKG_REQUEST_STATUS_FAILED = 3; 140 | // DKG_REQUEST_STATUS_TIMEDOUT defines the status of the DKG request which timed out 141 | DKG_REQUEST_STATUS_TIMEDOUT = 4; 142 | } 143 | 144 | // DKG Request 145 | message DKGRequest { 146 | // the unique request id 147 | uint64 id = 1; 148 | // participant set 149 | repeated DKGParticipant participants = 2; 150 | // threshold required to perform DKG 151 | uint32 threshold = 3; 152 | // asset types of vaults to be generated 153 | repeated AssetType vault_types = 4; 154 | // indicates if transferring assets to the newly generated vaults when the DKG request is completed 155 | bool enable_transfer = 5; 156 | // target number of the UTXOs to be transferred each time 157 | uint32 target_utxo_num = 6; 158 | // expiration time 159 | google.protobuf.Timestamp expiration = 7 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true]; 160 | // status 161 | DKGRequestStatus status = 8; 162 | } 163 | 164 | // DKG Completion Request 165 | message DKGCompletionRequest { 166 | // request id 167 | uint64 id = 1; 168 | // sender 169 | string sender = 2; 170 | // new vaults generated by DKG 171 | repeated string vaults = 3; 172 | // consensus address of the corresponding validator 173 | string consensus_address = 4; 174 | // hex encoded validator signature 175 | string signature = 5; 176 | } 177 | -------------------------------------------------------------------------------- /cmd/sided/cmd/commands.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | 11 | "cosmossdk.io/log" 12 | confixcmd "cosmossdk.io/tools/confix/cmd" 13 | dbm "github.com/cosmos/cosmos-db" 14 | "github.com/cosmos/cosmos-sdk/client" 15 | "github.com/cosmos/cosmos-sdk/client/debug" 16 | "github.com/cosmos/cosmos-sdk/client/flags" 17 | "github.com/cosmos/cosmos-sdk/client/keys" 18 | "github.com/cosmos/cosmos-sdk/client/pruning" 19 | "github.com/cosmos/cosmos-sdk/client/rpc" 20 | "github.com/cosmos/cosmos-sdk/client/snapshot" 21 | "github.com/cosmos/cosmos-sdk/server" 22 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 23 | "github.com/cosmos/cosmos-sdk/types/module" 24 | authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" 25 | "github.com/cosmos/cosmos-sdk/x/crisis" 26 | genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" 27 | 28 | "github.com/sideprotocol/side/app" 29 | ) 30 | 31 | func initRootCmd( 32 | rootCmd *cobra.Command, 33 | txConfig client.TxConfig, 34 | basicManager module.BasicManager, 35 | ) { 36 | rootCmd.AddCommand( 37 | genutilcli.InitCmd(basicManager, app.DefaultNodeHome), 38 | NewInPlaceTestnetCmd(addModuleInitFlags), 39 | debug.Cmd(), 40 | confixcmd.ConfigCommand(), 41 | pruning.Cmd(newApp, app.DefaultNodeHome), 42 | snapshot.Cmd(newApp), 43 | ) 44 | 45 | server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, addModuleInitFlags) 46 | 47 | // add keybase, auxiliary RPC, query, genesis, and tx child commands 48 | rootCmd.AddCommand( 49 | server.StatusCommand(), 50 | genesisCommand(txConfig, basicManager), 51 | queryCommand(), 52 | txCommand(), 53 | keys.Commands(), 54 | ) 55 | } 56 | 57 | func addModuleInitFlags(startCmd *cobra.Command) { 58 | crisis.AddModuleInitFlags(startCmd) 59 | } 60 | 61 | // genesisCommand builds genesis-related `sided genesis` command. Users may provide application specific commands as a parameter 62 | func genesisCommand(txConfig client.TxConfig, basicManager module.BasicManager, cmds ...*cobra.Command) *cobra.Command { 63 | cmd := genutilcli.Commands(txConfig, basicManager, app.DefaultNodeHome) 64 | 65 | for _, subCmd := range cmds { 66 | cmd.AddCommand(subCmd) 67 | } 68 | return cmd 69 | } 70 | 71 | func queryCommand() *cobra.Command { 72 | cmd := &cobra.Command{ 73 | Use: "query", 74 | Aliases: []string{"q"}, 75 | Short: "Querying subcommands", 76 | DisableFlagParsing: false, 77 | SuggestionsMinimumDistance: 2, 78 | RunE: client.ValidateCmd, 79 | } 80 | 81 | cmd.AddCommand( 82 | rpc.QueryEventForTxCmd(), 83 | rpc.ValidatorCommand(), 84 | server.QueryBlockCmd(), 85 | authcmd.QueryTxsByEventsCmd(), 86 | server.QueryBlocksCmd(), 87 | authcmd.QueryTxCmd(), 88 | server.QueryBlockResultsCmd(), 89 | ) 90 | cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") 91 | 92 | return cmd 93 | } 94 | 95 | func txCommand() *cobra.Command { 96 | cmd := &cobra.Command{ 97 | Use: "tx", 98 | Short: "Transactions subcommands", 99 | DisableFlagParsing: false, 100 | SuggestionsMinimumDistance: 2, 101 | RunE: client.ValidateCmd, 102 | } 103 | 104 | cmd.AddCommand( 105 | authcmd.GetSignCommand(), 106 | authcmd.GetSignBatchCommand(), 107 | authcmd.GetMultiSignCommand(), 108 | authcmd.GetMultiSignBatchCmd(), 109 | authcmd.GetValidateSignaturesCommand(), 110 | flags.LineBreak, 111 | authcmd.GetBroadcastCommand(), 112 | authcmd.GetEncodeCommand(), 113 | authcmd.GetDecodeCommand(), 114 | authcmd.GetSimulateCmd(), 115 | ) 116 | cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") 117 | 118 | return cmd 119 | } 120 | 121 | // newApp creates the application 122 | func newApp( 123 | logger log.Logger, 124 | db dbm.DB, 125 | traceStore io.Writer, 126 | appOpts servertypes.AppOptions, 127 | ) servertypes.Application { 128 | baseappOptions := server.DefaultBaseappOptions(appOpts) 129 | 130 | app := app.New( 131 | logger, db, traceStore, true, 132 | appOpts, 133 | baseappOptions..., 134 | ) 135 | 136 | return app 137 | } 138 | 139 | // appExport creates a new app (optionally at a given height) and exports state. 140 | func appExport( 141 | logger log.Logger, 142 | db dbm.DB, 143 | traceStore io.Writer, 144 | height int64, 145 | forZeroHeight bool, 146 | jailAllowedAddrs []string, 147 | appOpts servertypes.AppOptions, 148 | modulesToExport []string, 149 | ) (servertypes.ExportedApp, error) { 150 | var bApp *app.App 151 | 152 | // this check is necessary as we use the flag in x/upgrade. 153 | // we can exit more gracefully by checking the flag here. 154 | homePath, ok := appOpts.Get(flags.FlagHome).(string) 155 | if !ok || homePath == "" { 156 | return servertypes.ExportedApp{}, errors.New("application home not set") 157 | } 158 | 159 | viperAppOpts, ok := appOpts.(*viper.Viper) 160 | if !ok { 161 | return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") 162 | } 163 | 164 | // overwrite the FlagInvCheckPeriod 165 | viperAppOpts.Set(server.FlagInvCheckPeriod, 1) 166 | appOpts = viperAppOpts 167 | 168 | if height != -1 { 169 | bApp = app.New(logger, db, traceStore, false, appOpts) 170 | 171 | if err := bApp.LoadHeight(height); err != nil { 172 | return servertypes.ExportedApp{}, err 173 | } 174 | } else { 175 | bApp = app.New(logger, db, traceStore, true, appOpts) 176 | } 177 | 178 | return bApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) 179 | } 180 | 181 | var tempDir = func() string { 182 | dir, err := os.MkdirTemp("", "wasmd") 183 | if err != nil { 184 | panic("failed to create temp dir: " + err.Error()) 185 | } 186 | defer os.RemoveAll(dir) 187 | 188 | return dir 189 | } 190 | -------------------------------------------------------------------------------- /x/incentive/module/module.go: -------------------------------------------------------------------------------- 1 | package incentive 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | // this line is used by starport scaffolding # 1 9 | 10 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 11 | "github.com/spf13/cobra" 12 | 13 | abci "github.com/cometbft/cometbft/abci/types" 14 | 15 | "github.com/cosmos/cosmos-sdk/client" 16 | "github.com/cosmos/cosmos-sdk/codec" 17 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 18 | sdk "github.com/cosmos/cosmos-sdk/types" 19 | "github.com/cosmos/cosmos-sdk/types/module" 20 | 21 | "github.com/sideprotocol/side/x/incentive/client/cli" 22 | "github.com/sideprotocol/side/x/incentive/keeper" 23 | "github.com/sideprotocol/side/x/incentive/types" 24 | ) 25 | 26 | var ( 27 | _ module.AppModule = AppModule{} 28 | _ module.AppModuleBasic = AppModuleBasic{} 29 | ) 30 | 31 | // ---------------------------------------------------------------------------- 32 | // AppModuleBasic 33 | // ---------------------------------------------------------------------------- 34 | 35 | // AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement. 36 | type AppModuleBasic struct { 37 | cdc codec.BinaryCodec 38 | } 39 | 40 | func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { 41 | return AppModuleBasic{cdc: cdc} 42 | } 43 | 44 | // Name returns the name of the module as a string 45 | func (AppModuleBasic) Name() string { 46 | return types.ModuleName 47 | } 48 | 49 | // RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore 50 | func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 51 | types.RegisterCodec(cdc) 52 | } 53 | 54 | // RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message 55 | func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { 56 | types.RegisterInterfaces(reg) 57 | } 58 | 59 | // DefaultGenesis returns a default GenesisState for the module, marshaled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing 60 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { 61 | return cdc.MustMarshalJSON(types.DefaultGenesis()) 62 | } 63 | 64 | // ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form 65 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { 66 | var genState types.GenesisState 67 | if err := cdc.UnmarshalJSON(bz, &genState); err != nil { 68 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) 69 | } 70 | return genState.Validate() 71 | } 72 | 73 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module 74 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { 75 | if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { 76 | panic(err) 77 | } 78 | } 79 | 80 | // GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module 81 | func (AppModuleBasic) GetQueryCmd() *cobra.Command { 82 | return cli.GetQueryCmd(types.StoreKey) 83 | } 84 | 85 | // ---------------------------------------------------------------------------- 86 | // AppModule 87 | // ---------------------------------------------------------------------------- 88 | 89 | // AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement 90 | type AppModule struct { 91 | AppModuleBasic 92 | 93 | keeper keeper.Keeper 94 | } 95 | 96 | func NewAppModule( 97 | cdc codec.Codec, 98 | keeper keeper.Keeper, 99 | ) AppModule { 100 | return AppModule{ 101 | AppModuleBasic: NewAppModuleBasic(cdc), 102 | keeper: keeper, 103 | } 104 | } 105 | 106 | // IsOnePerModuleType implements the depinject.OnePerModuleType interface. 107 | func (am AppModule) IsOnePerModuleType() {} 108 | 109 | // IsAppModule implements the appmodule.AppModule interface. 110 | func (am AppModule) IsAppModule() {} 111 | 112 | // RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries 113 | func (am AppModule) RegisterServices(cfg module.Configurator) { 114 | types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) 115 | types.RegisterQueryServer(cfg.QueryServer(), am.keeper) 116 | } 117 | 118 | // RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) 119 | func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} 120 | 121 | // InitGenesis performs the module's genesis initialization. It returns no validator updates. 122 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { 123 | var genState types.GenesisState 124 | // Initialize global index to index in genesis state 125 | cdc.MustUnmarshalJSON(gs, &genState) 126 | 127 | InitGenesis(ctx, am.keeper, genState) 128 | 129 | return []abci.ValidatorUpdate{} 130 | } 131 | 132 | // ExportGenesis returns the module's exported genesis state as raw JSON bytes. 133 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { 134 | genState := ExportGenesis(ctx, am.keeper) 135 | return cdc.MustMarshalJSON(genState) 136 | } 137 | 138 | // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 139 | func (AppModule) ConsensusVersion() uint64 { return 1 } 140 | 141 | // BeginBlock contains the logic that is automatically triggered at the beginning of each block 142 | func (am AppModule) BeginBlock(_ context.Context) error { 143 | return nil 144 | } 145 | 146 | // EndBlock contains the logic that is automatically triggered at the end of each block 147 | func (am AppModule) EndBlock(_ context.Context) error { 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /x/btcbridge/module/module.go: -------------------------------------------------------------------------------- 1 | package btcbridge 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | // this line is used by starport scaffolding # 1 9 | 10 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 11 | "github.com/spf13/cobra" 12 | 13 | abci "github.com/cometbft/cometbft/abci/types" 14 | 15 | "github.com/cosmos/cosmos-sdk/client" 16 | "github.com/cosmos/cosmos-sdk/codec" 17 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 18 | sdk "github.com/cosmos/cosmos-sdk/types" 19 | "github.com/cosmos/cosmos-sdk/types/module" 20 | 21 | "github.com/sideprotocol/side/x/btcbridge/client/cli" 22 | "github.com/sideprotocol/side/x/btcbridge/keeper" 23 | "github.com/sideprotocol/side/x/btcbridge/types" 24 | ) 25 | 26 | var ( 27 | _ module.AppModule = AppModule{} 28 | _ module.AppModuleBasic = AppModuleBasic{} 29 | ) 30 | 31 | // ---------------------------------------------------------------------------- 32 | // AppModuleBasic 33 | // ---------------------------------------------------------------------------- 34 | 35 | // AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement. 36 | type AppModuleBasic struct { 37 | cdc codec.BinaryCodec 38 | } 39 | 40 | func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { 41 | return AppModuleBasic{cdc: cdc} 42 | } 43 | 44 | // Name returns the name of the module as a string 45 | func (AppModuleBasic) Name() string { 46 | return types.ModuleName 47 | } 48 | 49 | // RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore 50 | func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 51 | types.RegisterCodec(cdc) 52 | } 53 | 54 | // RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message 55 | func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { 56 | types.RegisterInterfaces(reg) 57 | } 58 | 59 | // DefaultGenesis returns a default GenesisState for the module, marshaled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing 60 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { 61 | return cdc.MustMarshalJSON(types.DefaultGenesis()) 62 | } 63 | 64 | // ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form 65 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { 66 | var genState types.GenesisState 67 | if err := cdc.UnmarshalJSON(bz, &genState); err != nil { 68 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) 69 | } 70 | return genState.Validate() 71 | } 72 | 73 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module 74 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { 75 | if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { 76 | panic(err) 77 | } 78 | } 79 | 80 | // GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to generate new transactions containing messages defined in the module 81 | func (a AppModuleBasic) GetTxCmd() *cobra.Command { 82 | return cli.GetTxCmd() 83 | } 84 | 85 | // GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module 86 | func (AppModuleBasic) GetQueryCmd() *cobra.Command { 87 | return cli.GetQueryCmd(types.StoreKey) 88 | } 89 | 90 | // ---------------------------------------------------------------------------- 91 | // AppModule 92 | // ---------------------------------------------------------------------------- 93 | 94 | // AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement 95 | type AppModule struct { 96 | AppModuleBasic 97 | 98 | keeper keeper.Keeper 99 | } 100 | 101 | func NewAppModule( 102 | cdc codec.Codec, 103 | keeper keeper.Keeper, 104 | ) AppModule { 105 | return AppModule{ 106 | AppModuleBasic: NewAppModuleBasic(cdc), 107 | keeper: keeper, 108 | } 109 | } 110 | 111 | // IsOnePerModuleType implements the depinject.OnePerModuleType interface. 112 | func (am AppModule) IsOnePerModuleType() {} 113 | 114 | // IsAppModule implements the appmodule.AppModule interface. 115 | func (am AppModule) IsAppModule() {} 116 | 117 | // RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries 118 | func (am AppModule) RegisterServices(cfg module.Configurator) { 119 | types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) 120 | types.RegisterQueryServer(cfg.QueryServer(), am.keeper) 121 | } 122 | 123 | // RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) 124 | func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} 125 | 126 | // InitGenesis performs the module's genesis initialization. It returns no validator updates. 127 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { 128 | var genState types.GenesisState 129 | // Initialize global index to index in genesis state 130 | cdc.MustUnmarshalJSON(gs, &genState) 131 | 132 | InitGenesis(ctx, am.keeper, genState) 133 | 134 | return []abci.ValidatorUpdate{} 135 | } 136 | 137 | // ExportGenesis returns the module's exported genesis state as raw JSON bytes. 138 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { 139 | genState := ExportGenesis(ctx, am.keeper) 140 | return cdc.MustMarshalJSON(genState) 141 | } 142 | 143 | // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 144 | func (AppModule) ConsensusVersion() uint64 { return 1 } 145 | 146 | // BeginBlock contains the logic that is automatically triggered at the beginning of each block 147 | func (am AppModule) BeginBlock(_ context.Context) error { 148 | return nil 149 | } 150 | 151 | // EndBlock contains the logic that is automatically triggered at the end of each block 152 | func (am AppModule) EndBlock(ctx context.Context) error { 153 | c := sdk.UnwrapSDKContext(ctx) 154 | EndBlocker(c, am.keeper) 155 | 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /api/side/incentive/query_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: side/incentive/query.proto 6 | 7 | package incentive 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Query_Params_FullMethodName = "/side.incentive.Query/Params" 23 | Query_Rewards_FullMethodName = "/side.incentive.Query/Rewards" 24 | Query_RewardStats_FullMethodName = "/side.incentive.Query/RewardStats" 25 | ) 26 | 27 | // QueryClient is the client API for Query service. 28 | // 29 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 30 | type QueryClient interface { 31 | // Params queries the parameters of the module. 32 | Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) 33 | // Rewards queries the rewards of the given address. 34 | Rewards(ctx context.Context, in *QueryRewardsRequest, opts ...grpc.CallOption) (*QueryRewardsResponse, error) 35 | // RewardStats queries total reward statistics. 36 | RewardStats(ctx context.Context, in *QueryRewardStatsRequest, opts ...grpc.CallOption) (*QueryRewardStatsResponse, error) 37 | } 38 | 39 | type queryClient struct { 40 | cc grpc.ClientConnInterface 41 | } 42 | 43 | func NewQueryClient(cc grpc.ClientConnInterface) QueryClient { 44 | return &queryClient{cc} 45 | } 46 | 47 | func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { 48 | out := new(QueryParamsResponse) 49 | err := c.cc.Invoke(ctx, Query_Params_FullMethodName, in, out, opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return out, nil 54 | } 55 | 56 | func (c *queryClient) Rewards(ctx context.Context, in *QueryRewardsRequest, opts ...grpc.CallOption) (*QueryRewardsResponse, error) { 57 | out := new(QueryRewardsResponse) 58 | err := c.cc.Invoke(ctx, Query_Rewards_FullMethodName, in, out, opts...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return out, nil 63 | } 64 | 65 | func (c *queryClient) RewardStats(ctx context.Context, in *QueryRewardStatsRequest, opts ...grpc.CallOption) (*QueryRewardStatsResponse, error) { 66 | out := new(QueryRewardStatsResponse) 67 | err := c.cc.Invoke(ctx, Query_RewardStats_FullMethodName, in, out, opts...) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return out, nil 72 | } 73 | 74 | // QueryServer is the server API for Query service. 75 | // All implementations must embed UnimplementedQueryServer 76 | // for forward compatibility 77 | type QueryServer interface { 78 | // Params queries the parameters of the module. 79 | Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) 80 | // Rewards queries the rewards of the given address. 81 | Rewards(context.Context, *QueryRewardsRequest) (*QueryRewardsResponse, error) 82 | // RewardStats queries total reward statistics. 83 | RewardStats(context.Context, *QueryRewardStatsRequest) (*QueryRewardStatsResponse, error) 84 | mustEmbedUnimplementedQueryServer() 85 | } 86 | 87 | // UnimplementedQueryServer must be embedded to have forward compatible implementations. 88 | type UnimplementedQueryServer struct { 89 | } 90 | 91 | func (UnimplementedQueryServer) Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) { 92 | return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") 93 | } 94 | func (UnimplementedQueryServer) Rewards(context.Context, *QueryRewardsRequest) (*QueryRewardsResponse, error) { 95 | return nil, status.Errorf(codes.Unimplemented, "method Rewards not implemented") 96 | } 97 | func (UnimplementedQueryServer) RewardStats(context.Context, *QueryRewardStatsRequest) (*QueryRewardStatsResponse, error) { 98 | return nil, status.Errorf(codes.Unimplemented, "method RewardStats not implemented") 99 | } 100 | func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {} 101 | 102 | // UnsafeQueryServer may be embedded to opt out of forward compatibility for this service. 103 | // Use of this interface is not recommended, as added methods to QueryServer will 104 | // result in compilation errors. 105 | type UnsafeQueryServer interface { 106 | mustEmbedUnimplementedQueryServer() 107 | } 108 | 109 | func RegisterQueryServer(s grpc.ServiceRegistrar, srv QueryServer) { 110 | s.RegisterService(&Query_ServiceDesc, srv) 111 | } 112 | 113 | func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 114 | in := new(QueryParamsRequest) 115 | if err := dec(in); err != nil { 116 | return nil, err 117 | } 118 | if interceptor == nil { 119 | return srv.(QueryServer).Params(ctx, in) 120 | } 121 | info := &grpc.UnaryServerInfo{ 122 | Server: srv, 123 | FullMethod: Query_Params_FullMethodName, 124 | } 125 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 126 | return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) 127 | } 128 | return interceptor(ctx, in, info, handler) 129 | } 130 | 131 | func _Query_Rewards_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 132 | in := new(QueryRewardsRequest) 133 | if err := dec(in); err != nil { 134 | return nil, err 135 | } 136 | if interceptor == nil { 137 | return srv.(QueryServer).Rewards(ctx, in) 138 | } 139 | info := &grpc.UnaryServerInfo{ 140 | Server: srv, 141 | FullMethod: Query_Rewards_FullMethodName, 142 | } 143 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 144 | return srv.(QueryServer).Rewards(ctx, req.(*QueryRewardsRequest)) 145 | } 146 | return interceptor(ctx, in, info, handler) 147 | } 148 | 149 | func _Query_RewardStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 150 | in := new(QueryRewardStatsRequest) 151 | if err := dec(in); err != nil { 152 | return nil, err 153 | } 154 | if interceptor == nil { 155 | return srv.(QueryServer).RewardStats(ctx, in) 156 | } 157 | info := &grpc.UnaryServerInfo{ 158 | Server: srv, 159 | FullMethod: Query_RewardStats_FullMethodName, 160 | } 161 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 162 | return srv.(QueryServer).RewardStats(ctx, req.(*QueryRewardStatsRequest)) 163 | } 164 | return interceptor(ctx, in, info, handler) 165 | } 166 | 167 | // Query_ServiceDesc is the grpc.ServiceDesc for Query service. 168 | // It's only intended for direct use with grpc.RegisterService, 169 | // and not to be introspected or modified (even as a copy) 170 | var Query_ServiceDesc = grpc.ServiceDesc{ 171 | ServiceName: "side.incentive.Query", 172 | HandlerType: (*QueryServer)(nil), 173 | Methods: []grpc.MethodDesc{ 174 | { 175 | MethodName: "Params", 176 | Handler: _Query_Params_Handler, 177 | }, 178 | { 179 | MethodName: "Rewards", 180 | Handler: _Query_Rewards_Handler, 181 | }, 182 | { 183 | MethodName: "RewardStats", 184 | Handler: _Query_RewardStats_Handler, 185 | }, 186 | }, 187 | Streams: []grpc.StreamDesc{}, 188 | Metadata: "side/incentive/query.proto", 189 | } 190 | -------------------------------------------------------------------------------- /app/export.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | storetypes "cosmossdk.io/store/types" 9 | cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" 10 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 13 | "github.com/cosmos/cosmos-sdk/x/staking" 14 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 15 | ) 16 | 17 | // ExportAppStateAndValidators exports the state of the application for a genesis 18 | // file. 19 | func (app *App) ExportAppStateAndValidators( 20 | forZeroHeight bool, 21 | jailAllowedAddrs []string, 22 | modulesToExport []string, 23 | ) (servertypes.ExportedApp, error) { 24 | // as if they could withdraw from the start of the next block 25 | ctx := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) 26 | 27 | // We export at last height + 1, because that's the height at which 28 | // Tendermint will start InitChain. 29 | height := app.LastBlockHeight() + 1 30 | if forZeroHeight { 31 | height = 0 32 | app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) 33 | } 34 | 35 | genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) 36 | if err != nil { 37 | return servertypes.ExportedApp{}, err 38 | } 39 | 40 | appState, err := json.MarshalIndent(genState, "", " ") 41 | if err != nil { 42 | return servertypes.ExportedApp{}, err 43 | } 44 | 45 | validators, err := staking.WriteValidators(ctx, app.StakingKeeper) 46 | return servertypes.ExportedApp{ 47 | AppState: appState, 48 | Validators: validators, 49 | Height: height, 50 | ConsensusParams: app.BaseApp.GetConsensusParams(ctx), 51 | }, err 52 | } 53 | 54 | // prepForZeroHeightGenesis prepares for a fresh genesis 55 | // 56 | // NOTE zero height genesis is a temporary feature which will be deprecated 57 | // in favour of export at a block height 58 | func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { 59 | applyAllowedAddrs := false 60 | 61 | // check if there is a allowed address list 62 | if len(jailAllowedAddrs) > 0 { 63 | applyAllowedAddrs = true 64 | } 65 | 66 | allowedAddrsMap := make(map[string]bool) 67 | 68 | for _, addr := range jailAllowedAddrs { 69 | _, err := sdk.ValAddressFromBech32(addr) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | allowedAddrsMap[addr] = true 74 | } 75 | 76 | /* Just to be safe, assert the invariants on current state. */ 77 | app.CrisisKeeper.AssertInvariants(ctx) 78 | 79 | /* Handle fee distribution state. */ 80 | 81 | // withdraw all validator commission 82 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 83 | valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 84 | if err != nil { 85 | panic(err) 86 | } 87 | 88 | _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz) 89 | return false 90 | }) 91 | 92 | // withdraw all delegator rewards 93 | dels, err := app.StakingKeeper.GetAllDelegations(ctx) 94 | if err != nil { 95 | panic(err) 96 | } 97 | for _, delegation := range dels { 98 | valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) 99 | if err != nil { 100 | panic(err) 101 | } 102 | 103 | delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress) 104 | 105 | _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) 106 | } 107 | 108 | // clear validator slash events 109 | app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) 110 | 111 | // clear validator historical rewards 112 | app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) 113 | 114 | // set context height to zero 115 | height := ctx.BlockHeight() 116 | ctx = ctx.WithBlockHeight(0) 117 | 118 | // reinitialize all validators 119 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 120 | valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 121 | if err != nil { 122 | panic(err) 123 | } 124 | // donate any unwithdrawn outstanding reward fraction tokens to the community pool 125 | scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) 126 | if err != nil { 127 | panic(err) 128 | } 129 | feePool, err := app.DistrKeeper.FeePool.Get(ctx) 130 | if err != nil { 131 | panic(err) 132 | } 133 | feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) 134 | if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { 135 | panic(err) 136 | } 137 | 138 | if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil { 139 | panic(err) 140 | } 141 | return false 142 | }) 143 | 144 | // reinitialize all delegations 145 | for _, del := range dels { 146 | valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) 147 | if err != nil { 148 | panic(err) 149 | } 150 | delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress) 151 | 152 | if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil { 153 | // never called as BeforeDelegationCreated always returns nil 154 | panic(fmt.Errorf("error while incrementing period: %w", err)) 155 | } 156 | 157 | if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil { 158 | // never called as AfterDelegationModified always returns nil 159 | panic(fmt.Errorf("error while creating a new delegation period record: %w", err)) 160 | } 161 | } 162 | 163 | // reset context height 164 | ctx = ctx.WithBlockHeight(height) 165 | 166 | /* Handle staking state. */ 167 | 168 | // iterate through redelegations, reset creation height 169 | app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { 170 | for i := range red.Entries { 171 | red.Entries[i].CreationHeight = 0 172 | } 173 | app.StakingKeeper.SetRedelegation(ctx, red) 174 | return false 175 | }) 176 | 177 | // iterate through unbonding delegations, reset creation height 178 | app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { 179 | for i := range ubd.Entries { 180 | ubd.Entries[i].CreationHeight = 0 181 | } 182 | app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) 183 | return false 184 | }) 185 | 186 | // Iterate through validators by power descending, reset bond heights, and 187 | // update bond intra-tx counters. 188 | store := ctx.KVStore(app.GetKey(stakingtypes.StoreKey)) 189 | iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) 190 | counter := int16(0) 191 | 192 | for ; iter.Valid(); iter.Next() { 193 | addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) 194 | validator, err := app.StakingKeeper.GetValidator(ctx, addr) 195 | if err != nil { 196 | panic(err) 197 | } 198 | 199 | validator.UnbondingHeight = 0 200 | if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { 201 | validator.Jailed = true 202 | } 203 | 204 | app.StakingKeeper.SetValidator(ctx, validator) 205 | counter++ 206 | } 207 | 208 | if err := iter.Close(); err != nil { 209 | app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err) 210 | return 211 | } 212 | 213 | _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) 214 | if err != nil { 215 | log.Fatal(err) 216 | } 217 | 218 | /* Handle slashing state. */ 219 | 220 | // reset start height on signing infos 221 | app.SlashingKeeper.IterateValidatorSigningInfos( 222 | ctx, 223 | func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { 224 | info.StartHeight = 0 225 | app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) 226 | return false 227 | }, 228 | ) 229 | } 230 | -------------------------------------------------------------------------------- /x/btcbridge/client/cli/tx.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/spf13/cobra" 12 | 13 | "github.com/btcsuite/btcd/btcutil/psbt" 14 | 15 | "github.com/cosmos/cosmos-sdk/client" 16 | "github.com/cosmos/cosmos-sdk/client/flags" 17 | "github.com/cosmos/cosmos-sdk/client/tx" 18 | sdk "github.com/cosmos/cosmos-sdk/types" 19 | 20 | // "github.com/cosmos/cosmos-sdk/client/flags" 21 | "github.com/sideprotocol/side/x/btcbridge/types" 22 | ) 23 | 24 | var DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) 25 | 26 | const ( 27 | // flagPacketTimeoutTimestamp = "packet-timeout-timestamp" 28 | listSeparator = "," 29 | ) 30 | 31 | // GetTxCmd returns the transaction commands for this module 32 | func GetTxCmd() *cobra.Command { 33 | cmd := &cobra.Command{ 34 | Use: types.ModuleName, 35 | Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), 36 | DisableFlagParsing: true, 37 | SuggestionsMinimumDistance: 2, 38 | RunE: client.ValidateCmd, 39 | } 40 | 41 | cmd.AddCommand(CmdSubmitBlocks()) 42 | cmd.AddCommand(CmdSubmitFeeRate()) 43 | cmd.AddCommand(CmdUpdateNonBtcRelayers()) 44 | cmd.AddCommand(CmdUpdateFeeProviders()) 45 | cmd.AddCommand(CmdWithdrawToBitcoin()) 46 | cmd.AddCommand(CmdSubmitSignatures()) 47 | cmd.AddCommand(CmdCompleteDKG()) 48 | 49 | return cmd 50 | } 51 | 52 | func CmdSubmitBlocks() *cobra.Command { 53 | cmd := &cobra.Command{ 54 | Use: "submit-blocks [file-path-to-block-headers.json]", 55 | Short: "Submit Bitcoin block headers to the chain", 56 | Args: cobra.ExactArgs(1), 57 | RunE: func(cmd *cobra.Command, args []string) (err error) { 58 | clientCtx, err := client.GetClientTxContext(cmd) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // read the block headers from the file 64 | blockHeaders, err := readBlockHeadersFromFile(args[0]) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | msg := types.NewMsgSubmitBlockHeaders( 70 | clientCtx.GetFromAddress().String(), 71 | blockHeaders, 72 | ) 73 | if err := msg.ValidateBasic(); err != nil { 74 | return err 75 | } 76 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 77 | }, 78 | } 79 | 80 | flags.AddTxFlagsToCmd(cmd) 81 | 82 | return cmd 83 | } 84 | 85 | func CmdSubmitFeeRate() *cobra.Command { 86 | cmd := &cobra.Command{ 87 | Use: "submit-fee-rate [fee rate]", 88 | Short: "Submit the latest fee rate of the bitcoin network", 89 | Args: cobra.ExactArgs(1), 90 | RunE: func(cmd *cobra.Command, args []string) (err error) { 91 | clientCtx, err := client.GetClientTxContext(cmd) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | feeRate, err := strconv.ParseInt(args[0], 10, 64) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | msg := types.NewMsgSubmitFeeRate( 102 | clientCtx.GetFromAddress().String(), 103 | feeRate, 104 | ) 105 | 106 | if err := msg.ValidateBasic(); err != nil { 107 | return err 108 | } 109 | 110 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 111 | }, 112 | } 113 | 114 | flags.AddTxFlagsToCmd(cmd) 115 | 116 | return cmd 117 | } 118 | 119 | func CmdUpdateNonBtcRelayers() *cobra.Command { 120 | cmd := &cobra.Command{ 121 | Use: "update-non-btc-relayers [relayers]", 122 | Short: "Update trusted non-btc asset relayers", 123 | Args: cobra.ExactArgs(1), 124 | RunE: func(cmd *cobra.Command, args []string) (err error) { 125 | clientCtx, err := client.GetClientTxContext(cmd) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | msg := types.NewMsgUpdateTrustedNonBtcRelayers( 131 | clientCtx.GetFromAddress().String(), 132 | strings.Split(args[0], listSeparator), 133 | ) 134 | 135 | if err := msg.ValidateBasic(); err != nil { 136 | return err 137 | } 138 | 139 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 140 | }, 141 | } 142 | 143 | flags.AddTxFlagsToCmd(cmd) 144 | 145 | return cmd 146 | } 147 | 148 | func CmdUpdateFeeProviders() *cobra.Command { 149 | cmd := &cobra.Command{ 150 | Use: "update-fee-providers [fee providers]", 151 | Short: "Update trusted fee providers", 152 | Args: cobra.ExactArgs(1), 153 | RunE: func(cmd *cobra.Command, args []string) (err error) { 154 | clientCtx, err := client.GetClientTxContext(cmd) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | msg := types.NewMsgUpdateTrustedFeeProviders( 160 | clientCtx.GetFromAddress().String(), 161 | strings.Split(args[0], listSeparator), 162 | ) 163 | 164 | if err := msg.ValidateBasic(); err != nil { 165 | return err 166 | } 167 | 168 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 169 | }, 170 | } 171 | 172 | flags.AddTxFlagsToCmd(cmd) 173 | 174 | return cmd 175 | } 176 | 177 | // Withdraw To Bitcoin 178 | func CmdWithdrawToBitcoin() *cobra.Command { 179 | cmd := &cobra.Command{ 180 | Use: "withdraw [amount]", 181 | Short: "Withdraw bitcoin asset to the given sender", 182 | Args: cobra.ExactArgs(1), 183 | RunE: func(cmd *cobra.Command, args []string) (err error) { 184 | clientCtx, err := client.GetClientTxContext(cmd) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | _, err = sdk.ParseCoinsNormalized(args[0]) 190 | if err != nil { 191 | return fmt.Errorf("invalid amount") 192 | } 193 | 194 | msg := types.NewMsgWithdrawToBitcoin( 195 | clientCtx.GetFromAddress().String(), 196 | args[0], 197 | ) 198 | 199 | if err := msg.ValidateBasic(); err != nil { 200 | return err 201 | } 202 | 203 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 204 | }, 205 | } 206 | 207 | flags.AddTxFlagsToCmd(cmd) 208 | 209 | return cmd 210 | } 211 | 212 | func CmdSubmitSignatures() *cobra.Command { 213 | cmd := &cobra.Command{ 214 | Use: "submit-signatures [psbt]", 215 | Short: "Submit the signed psbt", 216 | Args: cobra.ExactArgs(1), 217 | RunE: func(cmd *cobra.Command, args []string) (err error) { 218 | clientCtx, err := client.GetClientTxContext(cmd) 219 | if err != nil { 220 | return err 221 | } 222 | 223 | p, err := psbt.NewFromRawBytes(strings.NewReader(args[0]), true) 224 | if err != nil { 225 | return fmt.Errorf("invalid psbt") 226 | } 227 | 228 | signedTx, err := psbt.Extract(p) 229 | if err != nil { 230 | return fmt.Errorf("failed to extract tx from psbt") 231 | } 232 | 233 | msg := types.NewMsgSubmitSignatures( 234 | clientCtx.GetFromAddress().String(), 235 | signedTx.TxHash().String(), 236 | args[0], 237 | ) 238 | 239 | if err := msg.ValidateBasic(); err != nil { 240 | return err 241 | } 242 | 243 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 244 | }, 245 | } 246 | 247 | flags.AddTxFlagsToCmd(cmd) 248 | 249 | return cmd 250 | } 251 | 252 | // Complete DKG 253 | func CmdCompleteDKG() *cobra.Command { 254 | cmd := &cobra.Command{ 255 | Use: "complete-dkg [id] [vaults] [validator-address] [signature]", 256 | Short: "Complete dkg request with new vaults", 257 | Args: cobra.ExactArgs(4), 258 | RunE: func(cmd *cobra.Command, args []string) (err error) { 259 | clientCtx, err := client.GetClientTxContext(cmd) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | id, err := strconv.ParseUint(args[0], 10, 64) 265 | if err != nil { 266 | return err 267 | } 268 | 269 | vaults := strings.Split(args[1], listSeparator) 270 | 271 | msg := types.NewMsgCompleteDKG( 272 | clientCtx.GetFromAddress().String(), 273 | id, 274 | vaults, 275 | args[2], 276 | args[3], 277 | ) 278 | 279 | if err := msg.ValidateBasic(); err != nil { 280 | return err 281 | } 282 | 283 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 284 | }, 285 | } 286 | 287 | flags.AddTxFlagsToCmd(cmd) 288 | 289 | return cmd 290 | } 291 | 292 | // readBlockHeadersFromFile reads the block headers from the file 293 | func readBlockHeadersFromFile(filePath string) ([]*types.BlockHeader, error) { 294 | // read the file 295 | file, err := os.Open(filePath) 296 | if err != nil { 297 | return nil, err 298 | } 299 | defer file.Close() 300 | 301 | // read the block headers from the file 302 | var blockHeaders []*types.BlockHeader 303 | decoder := json.NewDecoder(file) 304 | if err := decoder.Decode(&blockHeaders); err != nil { 305 | return nil, err 306 | } 307 | return blockHeaders, nil 308 | } 309 | -------------------------------------------------------------------------------- /x/btcbridge/types/runes.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | 9 | "lukechampine.com/uint128" 10 | 11 | "github.com/btcsuite/btcd/txscript" 12 | "github.com/btcsuite/btcd/wire" 13 | ) 14 | 15 | const ( 16 | // runes protocol name 17 | RunesProtocolName = "runes" 18 | 19 | // runes magic number 20 | MagicNumber = txscript.OP_13 21 | 22 | // tag indicating that the following are edicts 23 | TagBody = 0 24 | 25 | // the number of components of each edict 26 | EdictLen = 4 27 | 28 | // sats in the runes output by default 29 | RunesOutValue = 546 30 | ) 31 | 32 | // ParseRunes parses the potential runes protocol from the given tx; 33 | // If no OP_RETURN found, no error returned 34 | // Only support edicts for now 35 | func ParseRunes(tx *wire.MsgTx) ([]*Edict, error) { 36 | for _, out := range tx.TxOut { 37 | tokenizer := txscript.MakeScriptTokenizer(0, out.PkScript) 38 | if !tokenizer.Next() || tokenizer.Err() != nil || tokenizer.Opcode() != txscript.OP_RETURN { 39 | continue 40 | } 41 | 42 | if !tokenizer.Next() || tokenizer.Err() != nil || tokenizer.Opcode() != MagicNumber { 43 | continue 44 | } 45 | 46 | var payload []byte 47 | 48 | for tokenizer.Next() { 49 | if txscript.IsSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= txscript.OP_PUSHDATA4 { 50 | payload = append(payload, tokenizer.Data()...) 51 | } else { 52 | return nil, ErrInvalidRunes 53 | } 54 | } 55 | 56 | if tokenizer.Err() != nil { 57 | return nil, ErrInvalidRunes 58 | } 59 | 60 | return ParseEdicts(tx, payload) 61 | } 62 | 63 | return nil, nil 64 | } 65 | 66 | // ParseEdicts parses the given payload to a set of edicts 67 | func ParseEdicts(tx *wire.MsgTx, payload []byte) ([]*Edict, error) { 68 | integers, err := DecodeVec(payload) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | if len(integers) < EdictLen+1 || len(integers[1:])%EdictLen != 0 || !integers[0].Equals(uint128.From64(TagBody)) { 74 | return nil, ErrInvalidRunes 75 | } 76 | 77 | integers = integers[1:] 78 | 79 | edicts := make([]*Edict, 0) 80 | 81 | for i := 0; i < len(integers); i = i + 4 { 82 | output := uint32(integers[i+3].Big().Uint64()) 83 | if output > uint32(len(tx.TxOut)) { 84 | return nil, ErrInvalidRunes 85 | } 86 | 87 | // actually we only support one edict for now, so delta is unnecessary 88 | edict := Edict{ 89 | Id: &RuneId{ 90 | Block: integers[i].Big().Uint64(), 91 | Tx: uint32(integers[i+1].Big().Uint64()), 92 | }, 93 | Amount: integers[i+2].String(), 94 | Output: output, 95 | } 96 | 97 | edicts = append(edicts, &edict) 98 | } 99 | 100 | return edicts, nil 101 | } 102 | 103 | // ParseEdict parses the given payload to edict 104 | func ParseEdict(payload []byte) (*Edict, error) { 105 | integers, err := DecodeVec(payload) 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | if len(integers) != EdictLen+1 && !integers[0].Equals(uint128.From64(TagBody)) { 111 | return nil, ErrInvalidRunes 112 | } 113 | 114 | return &Edict{ 115 | Id: &RuneId{ 116 | Block: integers[1].Big().Uint64(), 117 | Tx: uint32(integers[2].Big().Uint64()), 118 | }, 119 | Amount: integers[3].String(), 120 | Output: uint32(integers[4].Big().Uint64()), 121 | }, nil 122 | } 123 | 124 | // BuildEdictScript builds the edict script 125 | func BuildEdictScript(runeId string, amount uint128.Uint128, output uint32) ([]byte, error) { 126 | var id RuneId 127 | id.MustUnmarshalFromString(runeId) 128 | 129 | edict := Edict{ 130 | Id: &id, 131 | Amount: amount.String(), 132 | Output: output, 133 | } 134 | 135 | payload := []byte{TagBody} 136 | payload = append(payload, edict.MustMarshalLEB128()...) 137 | 138 | scriptBuilder := txscript.NewScriptBuilder() 139 | scriptBuilder.AddOp(txscript.OP_RETURN).AddOp(MagicNumber).AddData(payload) 140 | 141 | return scriptBuilder.Script() 142 | } 143 | 144 | func (id *RuneId) ToString() string { 145 | return fmt.Sprintf("%d:%d", id.Block, id.Tx) 146 | } 147 | 148 | func (id *RuneId) FromString(idStr string) error { 149 | parts := strings.Split(idStr, ":") 150 | if len(parts) != 2 { 151 | return ErrInvalidRuneId 152 | } 153 | 154 | block, err := strconv.ParseUint(parts[0], 10, 64) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | tx, err := strconv.ParseUint(parts[1], 10, 32) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | id.Block = block 165 | id.Tx = uint32(tx) 166 | 167 | return nil 168 | } 169 | 170 | func (id *RuneId) MustUnmarshalFromString(s string) { 171 | err := id.FromString(s) 172 | if err != nil { 173 | panic(err) 174 | } 175 | } 176 | 177 | func (id *RuneId) MarshalToBytes() []byte { 178 | bz := make([]byte, 8+4) 179 | 180 | binary.LittleEndian.PutUint64(bz, id.Block) 181 | binary.LittleEndian.PutUint32(bz, id.Tx) 182 | 183 | return bz 184 | } 185 | 186 | func (id *RuneId) UnmarshalFromBytes(bz []byte) { 187 | id.Block = binary.LittleEndian.Uint64(bz[:8]) 188 | id.Tx = binary.LittleEndian.Uint32(bz[8:]) 189 | } 190 | 191 | // Denom returns the corresponding denom for the runes voucher token 192 | func (id *RuneId) Denom() string { 193 | return fmt.Sprintf("%s/%s", RunesProtocolName, id.ToString()) 194 | } 195 | 196 | // FromDenom converts the denom to the rune id 197 | func (id *RuneId) FromDenom(denom string) { 198 | idStr := strings.TrimPrefix(denom, fmt.Sprintf("%s/", RunesProtocolName)) 199 | 200 | id.MustUnmarshalFromString(idStr) 201 | } 202 | 203 | // MarshalRuneIdFromString marshals the given id string 204 | func MarshalRuneIdFromString(s string) []byte { 205 | var id RuneId 206 | id.MustUnmarshalFromString(s) 207 | 208 | return id.MarshalToBytes() 209 | } 210 | 211 | // UnmarshalRuneId unmarshals the given bytes to the rune id 212 | func UnmarshalRuneId(bz []byte) RuneId { 213 | var id RuneId 214 | id.UnmarshalFromBytes(bz) 215 | 216 | return id 217 | } 218 | 219 | func (e *Edict) MustMarshalLEB128() []byte { 220 | amount := RuneAmountFromString(e.Amount) 221 | 222 | payload := make([]byte, 0) 223 | 224 | payload = append(payload, EncodeUint64(e.Id.Block)...) 225 | payload = append(payload, EncodeUint32(e.Id.Tx)...) 226 | payload = append(payload, EncodeUint128(&amount)...) 227 | payload = append(payload, EncodeUint32(e.Output)...) 228 | 229 | return payload 230 | } 231 | 232 | // RuneBalances defines a set of rune balances 233 | type RuneBalances []*RuneBalance 234 | 235 | // GetBalance gets the rune balance by id 236 | func (rbs RuneBalances) GetBalance(id string) (int, uint128.Uint128) { 237 | for i, balance := range rbs { 238 | if balance.Id == id { 239 | return i, RuneAmountFromString(balance.Amount) 240 | } 241 | } 242 | 243 | return -1, uint128.Zero 244 | } 245 | 246 | // Merge merges the another rune balances 247 | func (rbs RuneBalances) Merge(other RuneBalances) RuneBalances { 248 | var result RuneBalances 249 | result = append(result, rbs...) 250 | 251 | for _, ob := range other { 252 | i, b := result.GetBalance(ob.Id) 253 | if !b.IsZero() { 254 | result[i].Amount = b.Add(RuneAmountFromString(ob.Amount)).String() 255 | } else { 256 | result = append(result, ob) 257 | } 258 | } 259 | 260 | return result 261 | } 262 | 263 | // Update updates the balance to the specified amount for the given rune id 264 | // The rune balance will be removed if the given amount is zero 265 | // Assume that the given RuneBalances is compact 266 | func (rbs RuneBalances) Update(id string, amount uint128.Uint128) RuneBalances { 267 | for i, balance := range rbs { 268 | if balance.Id == id { 269 | if !amount.IsZero() { 270 | rbs[i].Amount = amount.String() 271 | } else { 272 | rbs = append(rbs[:i], rbs[i+1:]...) 273 | } 274 | 275 | break 276 | } 277 | } 278 | 279 | return rbs 280 | } 281 | 282 | // RuneAmountFromString converts the given string to the rune amount 283 | // Panic if any error occurred 284 | func RuneAmountFromString(s string) uint128.Uint128 { 285 | amount, err := uint128.FromString(s) 286 | if err != nil { 287 | panic(err) 288 | } 289 | 290 | return amount 291 | } 292 | 293 | // MarshalRuneAmount marshals the given amount 294 | func MarshalRuneAmount(amount uint128.Uint128) []byte { 295 | bz := make([]byte, 16) 296 | amount.PutBytes(bz) 297 | 298 | return bz 299 | } 300 | 301 | // MarshalRuneAmountFromString marshals the given amount string 302 | func MarshalRuneAmountFromString(s string) []byte { 303 | amount := RuneAmountFromString(s) 304 | 305 | return MarshalRuneAmount(amount) 306 | } 307 | 308 | // UnmarshalRuneAmount unmarshals the given bytes to the rune amount 309 | func UnmarshalRuneAmount(bz []byte) uint128.Uint128 { 310 | return uint128.FromBytes(bz) 311 | } 312 | --------------------------------------------------------------------------------