├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── interchaintest-e2e.yml │ ├── push-docker-image.yml │ └── release.yml ├── .gitignore ├── .gitpod.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── app ├── ante_handler.go ├── app.go ├── config.go ├── encoding.go ├── export.go ├── export_custom.go ├── genesis.go ├── helpers │ └── mock.go ├── keepers │ ├── keepers.go │ ├── keys.go │ └── querier.go ├── modules.go ├── params │ ├── amino.go │ ├── config.go │ ├── doc.go │ ├── encoding.go │ └── proto.go ├── posthandler.go ├── test_helper.go ├── testing │ └── test_suite.go ├── types.go └── upgrades │ ├── README.md │ ├── types.go │ ├── v021 │ ├── constants.go │ ├── upgrade_test.go │ └── upgrades.go │ └── v022 │ ├── constants.go │ ├── upgrades.go │ └── upgrades_test.go ├── banner.png ├── btsgutils ├── btsgcli │ ├── dynamic.go │ ├── flag_advice.go │ ├── index_cmd.go │ ├── parsers.go │ ├── query_cmd_wrap.go │ ├── string_formatter.go │ └── tx_cmd_wrap.go ├── generic_helper.go └── slice_helper.go ├── buf.gen.yaml ├── buf.work.yaml ├── chains.yaml ├── cmd └── bitsongd │ ├── cmd │ ├── config.go │ ├── custom_export.go │ ├── genesis.go │ ├── init.go │ └── root.go │ └── main.go ├── contrib ├── devtools │ ├── Makefile │ └── install-golangci-lint.sh └── docker │ ├── run_bitsongd.sh │ ├── setup_and_run.sh │ └── setup_bitsongd.sh ├── go.mod ├── go.sum ├── proto ├── README.md ├── bitsong │ ├── cadence │ │ └── v1 │ │ │ ├── cadence.proto │ │ │ ├── genesis.proto │ │ │ ├── query.proto │ │ │ └── tx.proto │ ├── fantoken │ │ └── v1beta1 │ │ │ ├── events.proto │ │ │ ├── fantoken.proto │ │ │ ├── genesis.proto │ │ │ ├── gov.proto │ │ │ ├── params.proto │ │ │ ├── query.proto │ │ │ └── tx.proto │ └── smartaccount │ │ └── v1beta1 │ │ ├── genesis.proto │ │ ├── models.proto │ │ ├── params.proto │ │ ├── query.proto │ │ └── tx.proto ├── buf.gen.gogo.yaml ├── buf.gen.pulsar.yaml ├── buf.gen.swagger.yaml ├── buf.lock └── buf.yaml ├── scripts ├── README.md ├── generate-swagger-docs.sh ├── makefiles │ ├── build.mk │ ├── docker.mk │ ├── e2e.mk │ ├── format.mk │ ├── hl.mk │ ├── localnet.mk │ ├── proto.mk │ └── tests.mk ├── polytone.sh ├── prep-release.sh ├── protocgen-any.sh ├── protocgen-pulsar.sh ├── protocgen.sh └── test_node.sh ├── server ├── start.go ├── types.go └── util.go ├── swagger ├── config.json ├── swagger-ibc.yaml ├── swagger-sdk.yaml ├── swagger-ui │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── index.html │ ├── oauth2-redirect.html │ ├── swagger-initializer.js │ ├── swagger-initializer.js.bak │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-es-bundle-core.js │ ├── swagger-ui-es-bundle-core.js.map │ ├── swagger-ui-es-bundle.js │ ├── swagger-ui-es-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ ├── swagger-ui.js.map │ └── swagger.yaml └── swagger.yaml ├── tests ├── README.md ├── bitsongibctesting │ └── chain.go ├── cw-orch │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin │ │ ├── grpc.rs │ │ ├── iba.rs │ │ └── slashing.rs │ │ └── lib.rs ├── ict │ ├── basic_start_test.go │ ├── basic_upgrade_test.go │ ├── ci.go │ ├── contracts │ │ ├── cw_template.wasm │ │ ├── polytone_listener.wasm │ │ ├── polytone_note.wasm │ │ ├── polytone_proxy.wasm │ │ ├── polytone_tester.wasm │ │ └── polytone_voice.wasm │ ├── go.mod │ ├── go.sum │ ├── helpers │ │ ├── common.go │ │ ├── cosmwasm.go │ │ └── types.go │ ├── module_pfm_test.go │ ├── setup.go │ ├── utils.go │ └── workflow │ │ └── cosmwasm.go └── localbitsong │ ├── README.md │ ├── docker-compose.yml │ └── scripts │ ├── add_keys.sh │ └── setup.sh ├── third_party └── proto │ ├── cosmos │ ├── base │ │ ├── query │ │ │ └── v1beta1 │ │ │ │ └── pagination.proto │ │ └── v1beta1 │ │ │ └── coin.proto │ └── upgrade │ │ └── v1beta1 │ │ └── upgrade.proto │ ├── cosmos_proto │ └── cosmos.proto │ ├── gogoproto │ └── gogo.proto │ ├── google │ └── api │ │ ├── annotations.proto │ │ └── http.proto │ ├── ibc │ └── core │ │ └── client │ │ └── v1 │ │ └── client.proto │ └── tendermint │ ├── abci │ └── types.proto │ ├── crypto │ ├── keys.proto │ └── proof.proto │ ├── libs │ └── bits │ │ └── types.proto │ ├── types │ ├── evidence.proto │ ├── params.proto │ ├── types.proto │ └── validator.proto │ └── version │ └── types.proto └── x ├── README.md ├── cadence ├── README.md ├── abci.go ├── abci_test.go ├── client │ └── cli │ │ ├── query.go │ │ └── tx.go ├── genesis.go ├── genesis_test.go ├── keeper │ ├── cadance.go │ ├── keeper.go │ ├── keeper_test.go │ ├── msg_server.go │ ├── msg_server_test.go │ ├── querier.go │ ├── querier_test.go │ └── testdata │ │ ├── clock_example.wasm │ │ └── cw_testburn.wasm ├── module.go ├── module_test.go ├── spec │ ├── 01_concepts.md │ ├── 02_state.md │ ├── 03_integration.md │ └── README.md └── types │ ├── cadence.pb.go │ ├── codec.go │ ├── codec_test.go │ ├── errors.go │ ├── genesis.pb.go │ ├── keys.go │ ├── msgs.go │ ├── msgs_test.go │ ├── params.go │ ├── params_test.go │ ├── query.pb.go │ ├── query.pb.gw.go │ ├── tx.pb.go │ └── tx.pb.gw.go ├── fantoken ├── client │ ├── cli │ │ ├── cli_test.go │ │ ├── flags.go │ │ ├── query.go │ │ └── tx.go │ └── proposal_handler.go ├── genesis.go ├── handler.go ├── handler_test.go ├── keeper │ ├── fantoken.go │ ├── fees.go │ ├── grpc_query.go │ ├── keeper.go │ ├── keeper_test.go │ ├── msg_server.go │ ├── params.go │ └── store.go ├── module.go ├── simulation │ └── genesis.go ├── spec │ ├── 01_concepts.md │ ├── 02_state.md │ ├── 03_messages.md │ ├── 04_events.md │ ├── 05_parameters.md │ ├── 06_client.md │ ├── 07_future_improvements.md │ ├── README.md │ └── img │ │ ├── fantoken_instance_lifecycle.svg │ │ ├── fantoken_object_lifecycle.svg │ │ └── image_code │ │ ├── fantoken_instance_lifecycle.dot │ │ └── fantoken_object_lifecycle.dot └── types │ ├── codec.go │ ├── errors.go │ ├── events.pb.go │ ├── expected_keepers.go │ ├── fantoken.go │ ├── fantoken.pb.go │ ├── genesis.go │ ├── genesis.pb.go │ ├── genesis_test.go │ ├── gov.go │ ├── gov.pb.go │ ├── keys.go │ ├── msgs.go │ ├── params.go │ ├── params.pb.go │ ├── query.pb.go │ ├── query.pb.gw.go │ ├── tx.pb.go │ ├── utils.go │ └── validation.go └── smart-account ├── README.md ├── ante ├── ante.go ├── ante_test.go ├── circuit_breaker.go ├── circuit_breaker_test.go ├── pubkey.go └── pubkey_test.go ├── authenticator ├── all_of.go ├── any_of.go ├── authentication_request.go ├── base_test.go ├── composite.go ├── composition_test.go ├── cosmwasm.go ├── cosmwasm_test.go ├── iface.go ├── manager.go ├── manager_test.go ├── message_filter.go ├── message_filter_test.go ├── replay_protection.go ├── requests.go ├── signature_authenticator.go ├── signature_authenticator_test.go └── spend_limits_test.go ├── client └── cli │ ├── query.go │ └── tx.go ├── genesis.go ├── images ├── authentication_flow.jpg ├── authenticator_manager.jpg ├── circuit_breaker.jpg └── keeper.jpg ├── integration_test.go ├── keeper ├── genesis.go ├── genesis_test.go ├── keeper.go ├── keeper_test.go ├── msg_server.go ├── msg_server_test.go ├── params.go ├── query.go └── query_params.go ├── module.go ├── post ├── post.go └── post_test.go ├── simulation └── helpers.go ├── testutils ├── bytecode │ ├── cosigner_authenticator.wasm │ ├── echo.wasm │ └── spend_limit_v1.0.0-alpha.1.wasm ├── contracts │ ├── build.sh │ ├── cosigner-authenticator │ │ ├── .cargo │ │ │ └── config │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ ├── lib.rs │ │ │ ├── multitest.rs │ │ │ ├── test_utils.rs │ │ │ ├── tests.rs │ │ │ └── types.rs │ └── echo │ │ ├── .cargo │ │ └── config │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── generic_authenticator.go ├── max_value_authenticator.go ├── spy_authenticator.go └── stateful_authenticator.go └── types ├── codec.go ├── expected_keepers.go ├── genesis.go ├── genesis.pb.go ├── genesis_test.go ├── keys.go ├── models.pb.go ├── msgs.go ├── params.go ├── params.pb.go ├── query.pb.go ├── query.pb.gw.go ├── telemetry.go └── tx.pb.go /.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/.dockerignore -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/.gitattributes -------------------------------------------------------------------------------- /.github/workflows/interchaintest-e2e.yml: -------------------------------------------------------------------------------- 1 | name: ictest E2E 2 | 3 | on: 4 | pull_request: 5 | push: 6 | tags: 7 | - "**" 8 | branches: 9 | - "main" 10 | - "master" 11 | 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | env: 17 | GO_VERSION: 1.23.0 18 | TAR_PATH: /tmp/bitsong-docker-image.tar 19 | IMAGE_NAME: bitsong-docker-image 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build-docker: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup Go ${{ env.GO_VERSION }} 33 | uses: actions/setup-go@v5 34 | with: 35 | go-version: ${{ env.GO_VERSION }} 36 | cache-dependency-path: interchaintest/go.sum 37 | 38 | - name: Set up Docker Buildx 39 | uses: docker/setup-buildx-action@v3 40 | 41 | - name: Build and export 42 | uses: docker/build-push-action@v5 43 | with: 44 | context: . 45 | tags: bitsong:local 46 | outputs: type=docker,dest=${{ env.TAR_PATH }} 47 | 48 | - name: Upload artifact 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: ${{ env.IMAGE_NAME }} 52 | path: ${{ env.TAR_PATH }} 53 | 54 | e2e-tests: 55 | needs: build-docker 56 | runs-on: ubuntu-latest 57 | strategy: 58 | matrix: 59 | # names of `make` commands to run tests 60 | test: 61 | - "e2e-basic" 62 | - "e2e-pfm" 63 | # - "e2e-polytone" 64 | # - "e2e-upgrade" 65 | fail-fast: false 66 | 67 | steps: 68 | - name: Set up Go ${{ env.GO_VERSION }} 69 | uses: actions/setup-go@v4 70 | with: 71 | go-version: ${{ env.GO_VERSION }} 72 | cache-dependency-path: interchaintest/go.sum 73 | 74 | - name: checkout chain 75 | uses: actions/checkout@v4 76 | 77 | - name: Download Tarball Artifact 78 | uses: actions/download-artifact@v4 79 | with: 80 | name: ${{ env.IMAGE_NAME }} 81 | path: /tmp 82 | 83 | - name: Load Docker Image 84 | run: | 85 | docker image load -i ${{ env.TAR_PATH }} 86 | docker image ls -a 87 | 88 | - name: Run Test 89 | id: run_test 90 | continue-on-error: true 91 | run: make ${{ matrix.test }} 92 | 93 | - name: Retry Failed Test 94 | if: steps.run_test.outcome == 'failure' 95 | run: | 96 | for i in 1 2; do 97 | echo "Retry attempt $i" 98 | if make ${{ matrix.test }}; then 99 | echo "Test passed on retry" 100 | exit 0 101 | fi 102 | done 103 | echo "Test failed after retries" 104 | exit 1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | .DS_Store 3 | *.swp 4 | *.swo 5 | *.swl 6 | *.swm 7 | *.swn 8 | .vscode 9 | .idea 10 | 11 | # Build 12 | vendor 13 | build 14 | dist 15 | tools-stamp 16 | 17 | # Data - ideally these don't exist 18 | baseapp/data/* 19 | client/lcd/keys/* 20 | cmd/bitsongcli/statik/statik.go 21 | mytestnet 22 | 23 | # Testing 24 | coverage.txt 25 | profile.out 26 | 27 | # heighliner 28 | heighliner/ 29 | 30 | # Vagrant 31 | .vagrant/ 32 | *.box 33 | *.log 34 | vagrant 35 | 36 | # IDE 37 | .idea/ 38 | *.iml 39 | 40 | # Graphviz 41 | dependency-graph.png 42 | 43 | # Latex 44 | *.aux 45 | *.out 46 | *.synctex.gz 47 | contract_tests/* 48 | buf-stamp 49 | 50 | data 51 | state_export.json 52 | 53 | github.com* 54 | gogoproto* 55 | target/ -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: go get && go build ./... && go test ./... && make 9 | command: go run . 10 | 11 | 12 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | project_name: go-bitsong 3 | 4 | release: 5 | github: 6 | owner: bitsongofficial 7 | name: go-bitsong 8 | 9 | builds: 10 | - skip: true 11 | 12 | archives: 13 | - format: tar.gz 14 | wrap_in_directory: true 15 | format_overrides: 16 | - goos: windows 17 | format: zip 18 | name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 19 | files: 20 | - LICENSE 21 | - README.md 22 | 23 | snapshot: 24 | name_template: SNAPSHOT-{{ .Commit }} 25 | 26 | changelog: 27 | skip: true 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build . -t bitsongofficial/go-bitsong:latest 2 | # docker run --rm -it bitsongofficial/go-bitsong:latest /bin/sh 3 | FROM golang:1.23-alpine AS go-builder 4 | 5 | # this comes from standard alpine nightly file 6 | # https://github.com/rust-lang/docker-rust-nightly/blob/master/alpine3.12/Dockerfile 7 | # with some changes to support our toolchain, etc 8 | SHELL ["/bin/sh", "-ecuxo", "pipefail"] 9 | # we probably want to default to latest and error 10 | # since this is predominantly for dev use 11 | # hadolint ignore=DL3018 12 | RUN apk add --no-cache ca-certificates build-base git 13 | # NOTE: add these to run with LEDGER_ENABLED=true 14 | # RUN apk add libusb-dev linux-headers 15 | 16 | WORKDIR /code 17 | 18 | # Download dependencies and CosmWasm libwasmvm if found. 19 | ADD go.mod go.sum ./ 20 | 21 | # Cosmwasm - Download correct libwasmvm version 22 | RUN ARCH=$(uname -m) && WASMVM_VERSION=$(go list -m github.com/CosmWasm/wasmvm/v2 | sed 's/.* //') && \ 23 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm_muslc.$ARCH.a \ 24 | -O /lib/libwasmvm_muslc.$ARCH.a && \ 25 | # verify checksum 26 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \ 27 | sha256sum /lib/libwasmvm_muslc.$ARCH.a | grep $(cat /tmp/checksums.txt | grep libwasmvm_muslc.$ARCH | cut -d ' ' -f 1) 28 | 29 | # Copy over code 30 | COPY . /code/ 31 | 32 | # force it to use static lib (from above) not standard libgo_cosmwasm.so file 33 | # then log output of file /code/build/bitsongd 34 | # then ensure static linking 35 | RUN LEDGER_ENABLED=false BUILD_TAGS=muslc LINK_STATICALLY=true make build \ 36 | && file /code/build/bitsongd \ 37 | && echo "Ensuring binary is statically linked ..." \ 38 | && (file /code/build/bitsongd | grep "statically linked") 39 | 40 | # -------------------------------------------------------- 41 | FROM alpine:3.17 42 | 43 | COPY --from=go-builder /code/build/bitsongd /usr/bin/bitsongd 44 | 45 | ENV HOME=/bitsongd 46 | WORKDIR $HOME 47 | 48 | # rest server, tendermint p2p, tendermint rpc 49 | EXPOSE 1317 26656 26657 50 | 51 | CMD ["/usr/bin/bitsongd"] 52 | 53 | ## To use as CLI: 54 | # alias bsd="docker run --rm -it -v ~/.bitsongd:/root/.bitsongd bitsong:v0.12323 bitsongd" -------------------------------------------------------------------------------- /app/config.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | 6 | dbm "github.com/cosmos/cosmos-db" 7 | "github.com/cosmos/cosmos-sdk/baseapp" 8 | "github.com/cosmos/cosmos-sdk/crypto/hd" 9 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 10 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 11 | 12 | "time" 13 | 14 | "github.com/cosmos/cosmos-sdk/testutil/network" 15 | sdk "github.com/cosmos/cosmos-sdk/types" 16 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 17 | ) 18 | 19 | // DefaultConfig returns a default configuration suitable for nearly all 20 | // testing requirements. 21 | func DefaultConfig() network.Config { 22 | encCfg := MakeEncodingConfig() 23 | chainId := "bitsong-test-1" 24 | 25 | return network.Config{ 26 | Codec: encCfg.Marshaler, 27 | TxConfig: encCfg.TxConfig, 28 | LegacyAmino: encCfg.Amino, 29 | InterfaceRegistry: encCfg.InterfaceRegistry, 30 | AccountRetriever: authtypes.AccountRetriever{}, 31 | AppConstructor: NewAppConstructor(chainId), 32 | GenesisState: AppModuleBasics.DefaultGenesis(encCfg.Marshaler), 33 | TimeoutCommit: 1 * time.Second / 2, 34 | ChainID: chainId, 35 | NumValidators: 1, 36 | BondDenom: sdk.DefaultBondDenom, 37 | MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), 38 | AccountTokens: sdk.TokensFromConsensusPower(100000, sdk.DefaultPowerReduction), 39 | StakingTokens: sdk.TokensFromConsensusPower(50000, sdk.DefaultPowerReduction), 40 | BondedTokens: sdk.TokensFromConsensusPower(10000, sdk.DefaultPowerReduction), 41 | PruningStrategy: "nothing", 42 | CleanupDir: true, 43 | SigningAlgo: string(hd.Secp256k1Type), 44 | KeyringOptions: []keyring.Option{}, 45 | } 46 | } 47 | 48 | func NewAppConstructor(chainId string) network.AppConstructor { 49 | return func(val network.ValidatorI) servertypes.Application { 50 | valCtx := val.GetCtx() 51 | appConfig := val.GetAppConfig() 52 | 53 | return NewBitsongApp( 54 | valCtx.Logger, dbm.NewMemDB(), nil, true, valCtx.Config.RootDir, EmptyAppOptions{}, EmptyWasmOpts, 55 | baseapp.SetMinGasPrices(appConfig.MinGasPrices), baseapp.SetChainID(chainId), 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/bitsongofficial/go-bitsong/app/params" 5 | "github.com/cosmos/cosmos-sdk/std" 6 | ) 7 | 8 | var encodingConfig params.EncodingConfig = MakeEncodingConfig() 9 | 10 | func GetEncodingConfig() params.EncodingConfig { 11 | return encodingConfig 12 | } 13 | 14 | // MakeEncodingConfig creates an EncodingConfig for testing 15 | func MakeEncodingConfig() params.EncodingConfig { 16 | encodingConfig := params.MakeEncodingConfig() 17 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 18 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 19 | AppModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 20 | AppModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 21 | return encodingConfig 22 | } 23 | -------------------------------------------------------------------------------- /app/helpers/mock.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "github.com/cometbft/cometbft/crypto" 5 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types" 6 | cmttypes "github.com/cometbft/cometbft/types" 7 | 8 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 9 | "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" 10 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 11 | ) 12 | 13 | var _ cmttypes.PrivValidator = PV{} 14 | 15 | // PV implements PrivValidator without any safety or persistence. 16 | // Only use it for testing. 17 | type PV struct { 18 | PrivKey cryptotypes.PrivKey 19 | } 20 | 21 | func NewPV() PV { 22 | return PV{ed25519.GenPrivKey()} 23 | } 24 | 25 | // GetPubKey implements PrivValidator interface 26 | func (pv PV) GetPubKey() (crypto.PubKey, error) { 27 | return cryptocodec.ToCmtPubKeyInterface(pv.PrivKey.PubKey()) 28 | } 29 | 30 | // SignVote implements PrivValidator interface 31 | func (pv PV) SignVote(chainID string, vote *tmproto.Vote) error { 32 | signBytes := cmttypes.VoteSignBytes(chainID, vote) 33 | sig, err := pv.PrivKey.Sign(signBytes) 34 | if err != nil { 35 | return err 36 | } 37 | vote.Signature = sig 38 | return nil 39 | } 40 | 41 | // SignProposal implements PrivValidator interface 42 | func (pv PV) SignProposal(chainID string, proposal *tmproto.Proposal) error { 43 | signBytes := cmttypes.ProposalSignBytes(chainID, proposal) 44 | sig, err := pv.PrivKey.Sign(signBytes) 45 | if err != nil { 46 | return err 47 | } 48 | proposal.Signature = sig 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /app/keepers/querier.go: -------------------------------------------------------------------------------- 1 | package keepers 2 | 3 | import ( 4 | storetypes "cosmossdk.io/store/types" 5 | ) 6 | 7 | // QuerierWrapper is a local wrapper around BaseApp that exports only the Queryable interface. 8 | // This is used to pass the baseApp to Async ICQ without exposing all methods 9 | type QuerierWrapper struct { 10 | querier storetypes.Queryable 11 | } 12 | 13 | var _ storetypes.Queryable = QuerierWrapper{} 14 | 15 | func NewQuerierWrapper(querier storetypes.Queryable) QuerierWrapper { 16 | return QuerierWrapper{querier: querier} 17 | } 18 | 19 | func (q QuerierWrapper) Query(req *storetypes.RequestQuery) (*storetypes.ResponseQuery, error) { 20 | return q.querier.Query(req) 21 | } 22 | -------------------------------------------------------------------------------- /app/params/amino.go: -------------------------------------------------------------------------------- 1 | //go:build test_amino 2 | // +build test_amino 3 | 4 | package params 5 | 6 | import ( 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | "github.com/cosmos/cosmos-sdk/codec/testutil" 9 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 10 | ) 11 | 12 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 13 | func MakeEncodingConfig() EncodingConfig { 14 | cdc := codec.New() 15 | interfaceRegistry := testutil.CodecOptions{AccAddressPrefix: "bitsong", ValAddressPrefix: "bitsongvaloper"}.NewInterfaceRegistry() 16 | marshaler := codec.NewAminoCodec(cdc) 17 | 18 | return EncodingConfig{ 19 | InterfaceRegistry: interfaceRegistry, 20 | Marshaler: marshaler, 21 | TxConfig: authtypes.StdTxConfig{Cdc: cdc}, 22 | Amino: cdc, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/params/config.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "cosmossdk.io/math" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | "github.com/cosmos/cosmos-sdk/types/address" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | e "github.com/pkg/errors" 9 | ) 10 | 11 | const ( 12 | CoinExponent = 6 13 | CoinType = 639 14 | CoinUnit = "btsg" 15 | MicroCoinUnit = "ubtsg" 16 | DefaultBondDenom = MicroCoinUnit 17 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address 18 | Bech32PrefixAccAddr = "bitsong" 19 | ) 20 | 21 | var ( 22 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key 23 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 24 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address 25 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 26 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key 27 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 28 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address 29 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 30 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key 31 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 32 | ) 33 | 34 | func init() { 35 | SetAddressPrefixes() 36 | RegisterTokenDenomination() 37 | } 38 | 39 | func SetAddressPrefixes() { 40 | config := sdk.GetConfig() 41 | config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 42 | config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 43 | config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 44 | 45 | // 639 is the registered coin type for BTSG 46 | // Following the coin type registered at https://github.com/satoshilabs/slips/blob/master/slip-0044.md 47 | config.SetCoinType(CoinType) 48 | config.SetPurpose(CoinExponent) 49 | 50 | // This is copied from the cosmos sdk v0.43.0-beta1 51 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141 52 | config.SetAddressVerifier(func(bytes []byte) error { 53 | if len(bytes) == 0 { 54 | return e.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty") 55 | } 56 | 57 | if len(bytes) > address.MaxAddrLen { 58 | e.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes)) 59 | } 60 | 61 | // TODO: Do we want to allow addresses of lengths other than 20 and 32 bytes? 62 | if len(bytes) != 20 && len(bytes) != 32 { 63 | return e.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) 64 | } 65 | 66 | return nil 67 | }) 68 | } 69 | 70 | func RegisterTokenDenomination() { 71 | err := sdk.RegisterDenom(CoinUnit, math.LegacyOneDec()) 72 | if err != nil { 73 | panic(err) 74 | } 75 | err = sdk.RegisterDenom(MicroCoinUnit, math.LegacyNewDecWithPrec(1, CoinExponent)) 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/params/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package params defines the simulation parameters in the simapp. 3 | It contains the default weights used for each transaction used on the module's 4 | simulation. These weights define the chance for a transaction to be simulated at 5 | any gived operation. 6 | You can repace the default values for the weights by providing a params.json 7 | file with the weights defined for each of the transaction operations: 8 | { 9 | "op_weight_msg_send": 60, 10 | "op_weight_msg_delegate": 100, 11 | } 12 | In the example above, the `MsgSend` has 60% chance to be simulated, while the 13 | `MsgDelegate` will always be simulated. 14 | */ 15 | package params 16 | -------------------------------------------------------------------------------- /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 | Marshaler codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /app/params/proto.go: -------------------------------------------------------------------------------- 1 | //go:build !test_amino 2 | // +build !test_amino 3 | 4 | package params 5 | 6 | import ( 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | "github.com/cosmos/cosmos-sdk/codec/testutil" 9 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 10 | ) 11 | 12 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 13 | func MakeEncodingConfig() EncodingConfig { 14 | amino := codec.NewLegacyAmino() 15 | interfaceRegistry := testutil.CodecOptions{AccAddressPrefix: "bitsong", ValAddressPrefix: "bitsongvaloper"}.NewInterfaceRegistry() 16 | marshaler := codec.NewProtoCodec(interfaceRegistry) 17 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) 18 | 19 | return EncodingConfig{ 20 | InterfaceRegistry: interfaceRegistry, 21 | Marshaler: marshaler, 22 | TxConfig: txCfg, 23 | Amino: amino, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/posthandler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | txsigning "cosmossdk.io/x/tx/signing" 5 | smartaccountkeeper "github.com/bitsongofficial/go-bitsong/x/smart-account/keeper" 6 | smartaccountpost "github.com/bitsongofficial/go-bitsong/x/smart-account/post" 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 10 | ) 11 | 12 | func NewPostHandler( 13 | cdc codec.Codec, 14 | smartAccountKeeper *smartaccountkeeper.Keeper, 15 | accountKeeper *authkeeper.AccountKeeper, 16 | sigModeHandler *txsigning.HandlerMap, 17 | ) sdk.PostHandler { 18 | return sdk.ChainPostDecorators( 19 | smartaccountpost.NewAuthenticatorPostDecorator( 20 | cdc, 21 | smartAccountKeeper, 22 | accountKeeper, 23 | sigModeHandler, 24 | // Add an empty handler here to enable a circuit breaker pattern 25 | sdk.ChainPostDecorators(sdk.Terminator{}), //nolint 26 | ), 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/types.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | abci "github.com/cometbft/cometbft/abci/types" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | "github.com/cosmos/cosmos-sdk/server/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | // CosmosApp implements the common methods for a Cosmos SDK-based application 12 | // specific blockchain. 13 | type CosmosApp interface { 14 | // Name The assigned name of the app. 15 | Name() string 16 | 17 | // LegacyAmino The application types codec. 18 | // NOTE: This shoult be sealed before being returned. 19 | LegacyAmino() *codec.LegacyAmino 20 | 21 | // BeginBlocker Application updates every begin block. 22 | BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) 23 | 24 | // EndBlocker Application updates every end block. 25 | EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) 26 | 27 | // InitChainer Application update at chain (i.e app) initialization. 28 | InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) 29 | 30 | // LoadHeight Loads the app at a given height. 31 | LoadHeight(height int64) error 32 | 33 | // ExportAppStateAndValidators Exports the state of the application for a genesis file. 34 | ExportAppStateAndValidators( 35 | forZeroHeight bool, jailAllowedAddrs []string, 36 | ) (types.ExportedApp, error) 37 | 38 | // ModuleAccountAddrs All the registered module account addreses. 39 | ModuleAccountAddrs() map[string]bool 40 | } 41 | -------------------------------------------------------------------------------- /app/upgrades/README.md: -------------------------------------------------------------------------------- 1 | # BitSong Upgrades 2 | 3 | This folder contains logic for every BitSong upgrade. 4 | 5 | -------------------------------------------------------------------------------- /app/upgrades/types.go: -------------------------------------------------------------------------------- 1 | package upgrades 2 | 3 | import ( 4 | store "cosmossdk.io/store/types" 5 | upgradetypes "cosmossdk.io/x/upgrade/types" 6 | "github.com/bitsongofficial/go-bitsong/app/keepers" 7 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/cosmos/cosmos-sdk/types/module" 10 | ) 11 | 12 | // Upgrade defines a struct containing necessary fields that a SoftwareUpgradeProposal 13 | // must have written, in order for the state migration to go smoothly. 14 | // An upgrade must implement this struct, and then set it in the app.go. 15 | // The app.go will then define the handler. 16 | type Upgrade struct { 17 | // Upgrade version name, for the upgrade handler, e.g. `v7` 18 | UpgradeName string 19 | 20 | // CreateUpgradeHandler defines the function that creates an upgrade handler 21 | CreateUpgradeHandler func(*module.Manager, module.Configurator, BaseAppParamManager, *keepers.AppKeepers) upgradetypes.UpgradeHandler 22 | 23 | // Store upgrades, should be used for any new modules introduced, new modules deleted, or store names renamed. 24 | StoreUpgrades store.StoreUpgrades 25 | } 26 | 27 | // BaseAppParamManager defines an interrace that BaseApp is expected to fullfil 28 | // that allows upgrade handlers to modify BaseApp parameters. 29 | type BaseAppParamManager interface { 30 | GetConsensusParams(ctx sdk.Context) tmproto.ConsensusParams 31 | StoreConsensusParams(ctx sdk.Context, cp tmproto.ConsensusParams) error 32 | } 33 | -------------------------------------------------------------------------------- /app/upgrades/v021/constants.go: -------------------------------------------------------------------------------- 1 | package v021 2 | 3 | import ( 4 | store "cosmossdk.io/store/types" 5 | "github.com/bitsongofficial/go-bitsong/app/upgrades" 6 | cadencetypes "github.com/bitsongofficial/go-bitsong/x/cadence/types" 7 | smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 8 | icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v8/types" 9 | ibchookstypes "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/types" 10 | wasmlctypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" 11 | ) 12 | 13 | const ( 14 | UpgradeName = "v021" 15 | // MaximumUnauthenticatedGas for smart account transactions to verify the fee payer 16 | MaximumUnauthenticatedGas = uint64(120_000) 17 | // IsSmartAccountActive is used for the smart account circuit breaker, smartaccounts are deactivated for v25 18 | IsSmartAccountActive = false 19 | 20 | // CircuitBreakerController is a DAODAO address, used only to deactivate the smart account module 21 | // https://daodao.zone/dao/bitsong13hmdq0slwmff7sej79kfa8mgnx4rl46nj2fvmlgu6u32tz6vfqesdfq4vm/home 22 | CircuitBreakerController = "bitsong13hmdq0slwmff7sej79kfa8mgnx4rl46nj2fvmlgu6u32tz6vfqesdfq4vm" 23 | ) 24 | 25 | var DefaultAllowedClients = []string{"07-tendermint", "09-localhost", wasmlctypes.Wasm} 26 | 27 | var Upgrade = upgrades.Upgrade{ 28 | UpgradeName: UpgradeName, 29 | CreateUpgradeHandler: CreateV021UpgradeHandler, 30 | StoreUpgrades: store.StoreUpgrades{ 31 | Added: []string{ 32 | icqtypes.StoreKey, 33 | wasmlctypes.StoreKey, 34 | ibchookstypes.StoreKey, 35 | cadencetypes.StoreKey, 36 | smartaccounttypes.StoreKey, 37 | }, 38 | Deleted: []string{}, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /app/upgrades/v022/constants.go: -------------------------------------------------------------------------------- 1 | package v022 2 | 3 | import ( 4 | store "cosmossdk.io/store/types" 5 | "github.com/bitsongofficial/go-bitsong/app/upgrades" 6 | distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" 7 | protocolpooltypes "github.com/cosmos/cosmos-sdk/x/protocolpool/types" 8 | ) 9 | 10 | const ( 11 | UpgradeName = "v022" 12 | PatchVal1 = "bitsongvaloper1qxw4fjged2xve8ez7nu779tm8ejw92rv0vcuqr" 13 | PatchVal2 = "bitsongvaloper1xnc32z84cc9vwftvv4w0v02a2slug3tjt6qyct" 14 | ) 15 | 16 | var Upgrade = upgrades.Upgrade{ 17 | UpgradeName: UpgradeName, 18 | CreateUpgradeHandler: CreateV022UpgradeHandler, 19 | StoreUpgrades: store.StoreUpgrades{Added: []string{ 20 | protocolpooltypes.StoreKey, 21 | }, Deleted: []string{}}, 22 | } 23 | 24 | type ConditionalJSON struct { 25 | PatchDelegationCount uint 26 | PatchedHistRewards []distrtypes.ValidatorHistoricalRewardsRecord 27 | ZeroSharesDelegation []ZeroSharesDelegation 28 | PatchedDelegation []PatchedDelegation 29 | NilDelegationCalculation []NilDelegationCalculation 30 | DistSlashStore DistrSlashObject 31 | } 32 | 33 | type DistrSlashObject struct { 34 | SlashEventCount uint64 `json:"total_slashes"` 35 | DistrSlashEvent []map[string][]Slash `json:"events"` 36 | } 37 | type DistrSlashEvent struct { 38 | Val string `json:"val_addr"` 39 | SlashEventCount uint64 `json:"total"` 40 | Slashes []Slash `json:"slash_events"` 41 | } 42 | type Slash struct { 43 | Height uint64 `json:"height"` 44 | Fraction string `json:"fraction"` 45 | Period uint64 `json:"period"` 46 | } 47 | 48 | type ZeroSharesDelegation struct { 49 | OperatorAddress string `json:"val_addr"` 50 | DelegatorAddress string `json:"del_addr"` 51 | } 52 | type PatchedDelegation struct { 53 | OperatorAddress string `json:"val_addr"` 54 | DelegatorAddress string `json:"del_addr"` 55 | PatchedDelegation string `json:"patch"` 56 | } 57 | type NilDelegationCalculation struct { 58 | OperatorAddress string `json:"val_addr"` 59 | DelegatorAddress string `json:"del_addr"` 60 | } 61 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/banner.png -------------------------------------------------------------------------------- /btsgutils/btsgcli/dynamic.go: -------------------------------------------------------------------------------- 1 | package btsgcli 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/bitsongofficial/go-bitsong/btsgutils" 10 | "github.com/cosmos/gogoproto/proto" 11 | ) 12 | 13 | type Descriptor interface { 14 | GetCustomFlagOverrides() map[string]string 15 | AttachToUse(str string) 16 | } 17 | 18 | // fields that are not provided as arguments 19 | var nonAttachableFields []string = []string{"sender", "pagination", "owner", "admin"} 20 | 21 | // attachFieldsToUse extracts fields from reqP proto message and dynamically appends them into Use field 22 | func attachFieldsToUse[reqP proto.Message](desc Descriptor) { 23 | req := btsgutils.MakeNew[reqP]() 24 | v := reflect.ValueOf(req).Type().Elem() // get underlying non-pointer struct 25 | var useField string 26 | for i := 0; i < v.NumField(); i++ { 27 | fn := pascalToKebab(v.Field(i).Name) 28 | 29 | // if a field is parsed from a flag, skip it 30 | if desc.GetCustomFlagOverrides()[fn] != "" || btsgutils.Contains(nonAttachableFields, fn) { 31 | continue 32 | } 33 | 34 | useField += fmt.Sprintf(" [%s]", fn) 35 | } 36 | 37 | desc.AttachToUse(useField) 38 | } 39 | 40 | // pascalToKebab converts PascalCase string to kebab-case string 41 | func pascalToKebab(s string) string { 42 | reg := regexp.MustCompile(`([a-z0-9])([A-Z])`) 43 | s = reg.ReplaceAllString(s, `${1}-${2}`) 44 | 45 | // Convert everything to lowercase 46 | return strings.ToLower(s) 47 | } 48 | -------------------------------------------------------------------------------- /btsgutils/btsgcli/flag_advice.go: -------------------------------------------------------------------------------- 1 | package btsgcli 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/pflag" 8 | ) 9 | 10 | type FlagAdvice struct { 11 | HasPagination bool 12 | 13 | // Map of FieldName -> FlagName 14 | CustomFlagOverrides map[string]string 15 | CustomFieldParsers map[string]CustomFieldParserFn 16 | 17 | // Tx sender value 18 | IsTx bool 19 | TxSenderFieldName string 20 | FromValue string 21 | } 22 | 23 | type FlagDesc struct { 24 | RequiredFlags []*pflag.FlagSet 25 | OptionalFlags []*pflag.FlagSet 26 | } 27 | 28 | type FieldReadLocation = bool 29 | 30 | const ( 31 | UsedArg FieldReadLocation = true 32 | UsedFlag FieldReadLocation = false 33 | ) 34 | 35 | // CustomFieldParser function. 36 | type CustomFieldParserFn = func(arg string, flags *pflag.FlagSet) (valueToSet any, usedArg FieldReadLocation, err error) 37 | 38 | func (f FlagAdvice) Sanitize() FlagAdvice { 39 | // map CustomFlagOverrides & CustomFieldParser keys to lower-case 40 | // initialize if uninitialized 41 | newFlagOverrides := make(map[string]string, len(f.CustomFlagOverrides)) 42 | for k, v := range f.CustomFlagOverrides { 43 | newFlagOverrides[strings.ToLower(k)] = v 44 | } 45 | f.CustomFlagOverrides = newFlagOverrides 46 | newFlagParsers := make(map[string]CustomFieldParserFn, len(f.CustomFieldParsers)) 47 | for k, v := range f.CustomFieldParsers { 48 | newFlagParsers[strings.ToLower(k)] = v 49 | } 50 | f.CustomFieldParsers = newFlagParsers 51 | return f 52 | } 53 | 54 | func FlagOnlyParser[v any](f func(fs *pflag.FlagSet) (v, error)) CustomFieldParserFn { 55 | return func(_arg string, fs *pflag.FlagSet) (any, FieldReadLocation, error) { 56 | t, err := f(fs) 57 | return t, UsedFlag, err 58 | } 59 | } 60 | 61 | // AddFlags from desc to cmd. 62 | // Required flags are marked as required. 63 | func AddFlags(cmd *cobra.Command, desc FlagDesc) { 64 | for i := 0; i < len(desc.OptionalFlags); i++ { 65 | cmd.Flags().AddFlagSet(desc.OptionalFlags[i]) 66 | } 67 | for i := 0; i < len(desc.RequiredFlags); i++ { 68 | fs := desc.RequiredFlags[i] 69 | cmd.Flags().AddFlagSet(fs) 70 | 71 | // mark all these flags as required. 72 | fs.VisitAll(func(flag *pflag.Flag) { 73 | err := cmd.MarkFlagRequired(flag.Name) 74 | if err != nil { 75 | panic(err) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /btsgutils/btsgcli/index_cmd.go: -------------------------------------------------------------------------------- 1 | package btsgcli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // Index command, but short is not set. That is left to caller. 10 | func IndexCmd(moduleName string) *cobra.Command { 11 | return &cobra.Command{ 12 | Use: moduleName, 13 | Short: fmt.Sprintf("Querying commands for the %s module", moduleName), 14 | DisableFlagParsing: true, 15 | SuggestionsMinimumDistance: 2, 16 | RunE: indexRunCmd, 17 | } 18 | } 19 | 20 | func indexRunCmd(cmd *cobra.Command, args []string) error { 21 | usageTemplate := `Usage:{{if .HasAvailableSubCommands}} 22 | {{.CommandPath}} [command]{{end}} 23 | 24 | {{if .HasAvailableSubCommands}}Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} 25 | {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} 26 | 27 | Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} 28 | ` 29 | cmd.SetUsageTemplate(usageTemplate) 30 | return cmd.Help() 31 | } 32 | -------------------------------------------------------------------------------- /btsgutils/btsgcli/string_formatter.go: -------------------------------------------------------------------------------- 1 | package btsgcli 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/cosmos/cosmos-sdk/version" 9 | ) 10 | 11 | type LongMetadata struct { 12 | BinaryName string 13 | CommandPrefix string 14 | Short string 15 | 16 | // Newline Example: 17 | ExampleHeader string 18 | } 19 | 20 | func NewLongMetadata(moduleName string) *LongMetadata { 21 | commandPrefix := fmt.Sprintf("$ %s q %s", version.AppName, moduleName) 22 | return &LongMetadata{ 23 | BinaryName: version.AppName, 24 | CommandPrefix: commandPrefix, 25 | } 26 | } 27 | 28 | func (m *LongMetadata) WithShort(short string) *LongMetadata { 29 | m.Short = short 30 | return m 31 | } 32 | 33 | func FormatLongDesc(longString string, meta *LongMetadata) string { 34 | template, err := template.New("long_description").Parse(longString) 35 | if err != nil { 36 | panic("incorrectly configured long message") 37 | } 38 | bld := strings.Builder{} 39 | meta.ExampleHeader = "\n\nExample:" 40 | err = template.Execute(&bld, meta) 41 | if err != nil { 42 | panic("incorrectly configured long message") 43 | } 44 | return strings.TrimSpace(bld.String()) 45 | } 46 | 47 | func FormatLongDescDirect(longString string, moduleName string) string { 48 | return FormatLongDesc(longString, NewLongMetadata(moduleName)) 49 | } 50 | -------------------------------------------------------------------------------- /btsgutils/generic_helper.go: -------------------------------------------------------------------------------- 1 | package btsgutils 2 | 3 | import "reflect" 4 | 5 | // MakeNew makes a new instance of generic T. 6 | // if T is a pointer, makes a new instance of the underlying struct via reflection, 7 | // and then a pointer to it. 8 | func MakeNew[T any]() T { 9 | var v T 10 | if typ := reflect.TypeOf(v); typ.Kind() == reflect.Ptr { 11 | elem := typ.Elem() 12 | //nolint:forcetypeassert 13 | return reflect.New(elem).Interface().(T) // must use reflect 14 | } else { 15 | return *new(T) // v is not ptr, alloc with new 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: . 5 | opt: plugins=interfacetype+grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types 6 | - name: grpc-gateway 7 | out: . 8 | opt: logtostderr=true,allow_colon_final_segments=true -------------------------------------------------------------------------------- /buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | directories: 3 | - proto -------------------------------------------------------------------------------- /chains.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to create docker images using the heighliner binary. 2 | # see: https://github.com/strangelove-ventures/heighliner 3 | 4 | - name: bitsong 5 | dockerfile: cosmos 6 | build-target: make install 7 | binaries: 8 | - /go/bin/bitsongd 9 | build-env: 10 | - LEDGER_ENABLED=false 11 | - BUILD_TAGS=muslc -------------------------------------------------------------------------------- /cmd/bitsongd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "cosmossdk.io/log" 7 | "github.com/bitsongofficial/go-bitsong/app/params" 8 | 9 | "github.com/bitsongofficial/go-bitsong/app" 10 | "github.com/bitsongofficial/go-bitsong/cmd/bitsongd/cmd" 11 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 12 | ) 13 | 14 | func main() { 15 | params.SetAddressPrefixes() 16 | 17 | rootCmd, _ := cmd.NewRootCmd() 18 | if err := svrcmd.Execute(rootCmd, "BITSONGD", app.DefaultNodeHome); err != nil { 19 | log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) 20 | os.Exit(1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contrib/devtools/install-golangci-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | installer="$(mktemp)" 6 | trap "rm -f ${installer}" EXIT 7 | 8 | GOBIN="${1}" 9 | CURL="$(which curl)" 10 | HASHSUM="${2}" 11 | 12 | f_sha256() { 13 | local l_file 14 | l_file=$1 15 | python -sBc "import hashlib;print(hashlib.sha256(open('$l_file','rb').read()).hexdigest())" 16 | } 17 | 18 | get_latest_release() { 19 | "${CURL}" --silent "https://api.github.com/repos/$1/releases/latest" | 20 | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' 21 | } 22 | 23 | VERSION="$(get_latest_release golangci/golangci-lint)" 24 | 25 | echo "Downloading golangci-lint ${VERSION} installer ..." >&2 26 | "${CURL}" -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/${VERSION}/install.sh" >"${installer}" 27 | 28 | echo "Checking hashsum ..." >&2 29 | # [ "${HASHSUM}" = "$(f_sha256 ${installer})" ] 30 | chmod +x "${installer}" 31 | 32 | echo "Launching installer ..." >&2 33 | exec "${installer}" -d -b "${GOBIN}" "${VERSION}" 34 | -------------------------------------------------------------------------------- /contrib/docker/run_bitsongd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -n "$1"; then 4 | # need -R not -r to copy hidden files 5 | cp -R "$1/.bitsongd" /root 6 | fi 7 | 8 | mkdir -p /root/log 9 | bitsongd start --rpc.laddr tcp://0.0.0.0:26657 --minimum-gas-prices 0.0001ubtsg --trace 10 | -------------------------------------------------------------------------------- /contrib/docker/setup_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./setup_bitsongd.sh "$@" 4 | ./run_bitsongd.sh -------------------------------------------------------------------------------- /proto/README.md: -------------------------------------------------------------------------------- 1 | ## Manual Protobuf build 2 | 3 | set env variables: 4 | ```sh 5 | GOLANG_PROTOBUF_VERSION=1.36.6 6 | GRPC_GATEWAY_VERSION=1.16.0 7 | ``` 8 | 9 | run: 10 | ```sh 11 | go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest 12 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} 13 | go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} 14 | go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} 15 | ``` 16 | then run: 17 | ```sh 18 | git clone https://github.com/cosmos/gogoproto.git; 19 | cd gogoproto; \ 20 | go mod download; \ 21 | make install 22 | ``` 23 | once installed, in the root of your project 24 | ```sh 25 | # requires buf to be installed: https://buf.build/docs/installation/ 26 | cd ../proto 27 | buf mod update 28 | cd .. 29 | buf generate 30 | ``` 31 | 32 | then, move the generated proto file into the right places: 33 | ```sh 34 | cp -r ./github.com/bitsongofficial/go-bitsong/x/* x/ 35 | # cp -r ./github.com///x/* x/ 36 | ``` 37 | 38 | then you can clean the repo: 39 | ```sh 40 | rm -rf ./github.com 41 | ``` -------------------------------------------------------------------------------- /proto/bitsong/cadence/v1/cadence.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.cadence.v1; 3 | 4 | option go_package = "github.com/bitsongofficial/go-bitsong/x/cadence/types"; 5 | 6 | // This object is used to store the contract address and the 7 | // jail status of the contract. 8 | message CadenceContract { 9 | // The address of the contract. 10 | string contract_address = 1; 11 | // The jail status of the contract. 12 | bool is_jailed = 2; 13 | } -------------------------------------------------------------------------------- /proto/bitsong/cadence/v1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.cadence.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos/base/v1beta1/coin.proto"; 6 | import "bitsong/cadence/v1/cadence.proto"; 7 | 8 | option go_package = "github.com/bitsongofficial/go-bitsong/x/cadence/types"; 9 | 10 | // GenesisState - initial state of module 11 | message GenesisState { 12 | // Params of this module 13 | Params params = 1 [ 14 | (gogoproto.nullable) = false, 15 | (gogoproto.jsontag) = "params,omitempty" 16 | ]; 17 | } 18 | 19 | // Params defines the set of module parameters. 20 | message Params { 21 | // contract_gas_limit defines the maximum amount of gas that can be used by a contract. 22 | uint64 contract_gas_limit = 1 [ 23 | (gogoproto.jsontag) = "contract_gas_limit,omitempty", 24 | (gogoproto.moretags) = "yaml:\"contract_gas_limit\"" 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /proto/bitsong/cadence/v1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.cadence.v1; 3 | 4 | import "cosmos/base/query/v1beta1/pagination.proto"; 5 | import "gogoproto/gogo.proto"; 6 | import "google/api/annotations.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | import "bitsong/cadence/v1/genesis.proto"; 9 | import "bitsong/cadence/v1/cadence.proto"; 10 | 11 | option go_package = "github.com/bitsongofficial/go-bitsong/x/cadence/types"; 12 | 13 | // Query defines the gRPC querier service. 14 | service Query { 15 | // CadenceContracts 16 | rpc CadenceContracts(QueryCadenceContracts) 17 | returns (QueryCadenceContractsResponse) { 18 | option (google.api.http).get = 19 | "/bitsong/cadence/v1/contracts"; 20 | } 21 | // CadenceContract 22 | rpc CadenceContract(QueryCadenceContract) 23 | returns (QueryCadenceContractResponse) { 24 | option (google.api.http).get = 25 | "/bitsong/cadence/v1/contracts/{contract_address}"; 26 | } 27 | // Params 28 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 29 | option (google.api.http).get = "/bitsong/cadence/v1/params"; 30 | } 31 | } 32 | 33 | // QueryCadenceContracts is the request type to get all contracts. 34 | message QueryCadenceContracts { 35 | // pagination defines an optional pagination for the request. 36 | cosmos.base.query.v1beta1.PageRequest pagination = 1; 37 | } 38 | 39 | // QueryCadenceContractsResponse is the response type for the Query/CadenceContracts RPC method. 40 | message QueryCadenceContractsResponse { 41 | // cadence_contracts are the cadence contract s. 42 | repeated CadenceContract cadence_contracts = 1 [ (gogoproto.nullable) = false ]; 43 | // pagination defines the pagination in the response. 44 | cosmos.base.query.v1beta1.PageResponse pagination = 2; 45 | } 46 | 47 | // QueryCadenceContract is the request type to get a single contract. 48 | message QueryCadenceContract { 49 | // contract_address is the address of the contract to query. 50 | string contract_address = 1; 51 | } 52 | 53 | // QueryCadenceContractResponse is the response type for the Query/CadenceContract RPC method. 54 | message QueryCadenceContractResponse { 55 | // contract is the cadence contract . 56 | CadenceContract cadence_contract = 1 [(gogoproto.nullable) = false]; 57 | } 58 | 59 | // QueryParams is the request type to get all module params. 60 | message QueryParamsRequest {} 61 | 62 | // QueryCadenceContractsResponse is the response type for the Query/CadenceContracts RPC method. 63 | message QueryParamsResponse { 64 | Params params = 1 [(gogoproto.jsontag) = "params", (gogoproto.moretags) = "yaml:\"params\""]; 65 | } 66 | -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/events.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 7 | 8 | message EventIssue { string denom = 1; } 9 | 10 | message EventDisableMint { string denom = 1; } 11 | 12 | message EventMint { 13 | string recipient = 1; 14 | string coin = 2; 15 | } 16 | 17 | message EventBurn { 18 | string sender = 1; 19 | string coin = 2; 20 | } 21 | 22 | message EventSetAuthority { 23 | string denom = 1; 24 | string old_authority = 2 [ (gogoproto.moretags) = "yaml:\"old_authority\"" ]; 25 | string new_authority = 3 [ (gogoproto.moretags) = "yaml:\"new_authority\"" ]; 26 | } 27 | 28 | message EventSetMinter { 29 | string denom = 1; 30 | string old_minter = 2 [ (gogoproto.moretags) = "yaml:\"old_minter\"" ]; 31 | string new_minter = 3 [ (gogoproto.moretags) = "yaml:\"new_minter\"" ]; 32 | } 33 | 34 | message EventSetUri { string denom = 1; } -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/fantoken.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "cosmos/base/v1beta1/coin.proto"; 5 | import "gogoproto/gogo.proto"; 6 | 7 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 8 | option (gogoproto.goproto_getters_all) = false; 9 | 10 | message Metadata { 11 | // name defines the name of the fantoken (eg: Kitty Punk) 12 | string name = 1; 13 | 14 | // symbol is the token symbol usually shown on exchanges (eg: KITTY) 15 | string symbol = 2; 16 | 17 | // URI to a document (on or off-chain) that contains additional 18 | // information.Optional. 19 | string uri = 3 [ (gogoproto.customname) = "URI" ]; 20 | 21 | // sdk.AccAddress allowed to set a new uri 22 | string authority = 4; 23 | } 24 | 25 | // FanToken defines a standard for the fungible token 26 | message FanToken { 27 | option (gogoproto.goproto_getters) = false; 28 | option (gogoproto.goproto_stringer) = false; 29 | 30 | // denom represents the string name of the given denom unit (e.g ft). 31 | string denom = 1; 32 | 33 | string max_supply = 2 [ 34 | (gogoproto.customtype) = "cosmossdk.io/math.Int", 35 | (gogoproto.moretags) = "yaml:\"max_supply\"", 36 | (gogoproto.nullable) = false 37 | ]; 38 | 39 | // sdk.AccAddress allowed to mint new fantoken 40 | string minter = 3; 41 | 42 | Metadata meta_data = 4 [ 43 | (gogoproto.moretags) = "yaml:\"meta_data\"", 44 | (gogoproto.nullable) = false 45 | ]; 46 | } -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "bitsong/fantoken/v1beta1/fantoken.proto"; 6 | import "bitsong/fantoken/v1beta1/params.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | 9 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 10 | 11 | // GenesisState defines the fantoken module's genesis state 12 | message GenesisState { 13 | bitsong.fantoken.v1beta1.Params params = 1 [ (gogoproto.nullable) = false ]; 14 | 15 | repeated bitsong.fantoken.v1beta1.FanToken fan_tokens = 2 16 | [ (gogoproto.nullable) = false ]; 17 | } -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/gov.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "cosmos/base/v1beta1/coin.proto"; 5 | import "gogoproto/gogo.proto"; 6 | 7 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 8 | option (gogoproto.goproto_getters_all) = false; 9 | 10 | message UpdateFeesProposal { 11 | option (gogoproto.equal) = true; 12 | option (gogoproto.goproto_stringer) = false; 13 | 14 | string title = 1; 15 | string description = 2; 16 | 17 | cosmos.base.v1beta1.Coin issue_fee = 3 [ 18 | (gogoproto.moretags) = "yaml:\"issue_fee\"", 19 | (gogoproto.nullable) = false 20 | ]; 21 | 22 | cosmos.base.v1beta1.Coin mint_fee = 4 [ 23 | (gogoproto.moretags) = "yaml:\"mint_fee\"", 24 | (gogoproto.nullable) = false 25 | ]; 26 | 27 | cosmos.base.v1beta1.Coin burn_fee = 5 [ 28 | (gogoproto.moretags) = "yaml:\"burn_fee\"", 29 | (gogoproto.nullable) = false 30 | ]; 31 | } 32 | 33 | message UpdateFeesProposalWithDeposit { 34 | option (gogoproto.goproto_stringer) = true; 35 | 36 | string title = 1; 37 | string description = 2; 38 | string issue_fee = 3; 39 | string mint_fee = 4; 40 | string burn_fee = 5; 41 | string deposit = 7; 42 | } -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "cosmos/base/v1beta1/coin.proto"; 5 | import "gogoproto/gogo.proto"; 6 | 7 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 8 | option (gogoproto.goproto_getters_all) = false; 9 | 10 | // Params defines fantoken module's parameters 11 | message Params { 12 | option (gogoproto.equal) = true; 13 | option (gogoproto.goproto_stringer) = false; 14 | 15 | cosmos.base.v1beta1.Coin issue_fee = 1 [ 16 | (gogoproto.moretags) = "yaml:\"issue_fee\"", 17 | (gogoproto.nullable) = false 18 | ]; 19 | 20 | cosmos.base.v1beta1.Coin mint_fee = 2 [ 21 | (gogoproto.moretags) = "yaml:\"mint_fee\"", 22 | (gogoproto.nullable) = false 23 | ]; 24 | 25 | cosmos.base.v1beta1.Coin burn_fee = 3 [ 26 | (gogoproto.moretags) = "yaml:\"burn_fee\"", 27 | (gogoproto.nullable) = false 28 | ]; 29 | } -------------------------------------------------------------------------------- /proto/bitsong/fantoken/v1beta1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.fantoken.v1beta1; 3 | 4 | import "cosmos/base/v1beta1/coin.proto"; 5 | import "cosmos/base/query/v1beta1/pagination.proto"; 6 | import "gogoproto/gogo.proto"; 7 | import "google/api/annotations.proto"; 8 | import "bitsong/fantoken/v1beta1/fantoken.proto"; 9 | import "bitsong/fantoken/v1beta1/params.proto"; 10 | 11 | option go_package = "github.com/bitsongofficial/go-bitsong/x/fantoken/types"; 12 | 13 | // Query creates service with fantoken as RPC 14 | service Query { 15 | 16 | // FanToken returns fantoken with fantoken name 17 | rpc FanToken(QueryFanTokenRequest) returns (QueryFanTokenResponse) { 18 | option (google.api.http).get = "/bitsong/fantoken/v1beta1/denom/{denom}"; 19 | } 20 | 21 | // FanTokens returns the fantoken list 22 | rpc FanTokens(QueryFanTokensRequest) returns (QueryFanTokensResponse) { 23 | option (google.api.http).get = "/bitsong/fantoken/v1beta1/fantokens"; 24 | } 25 | 26 | // Params queries the fantoken parameters 27 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 28 | option (google.api.http).get = "/bitsong/fantoken/v1beta1/params"; 29 | } 30 | } 31 | 32 | // QueryFanTokenRequest is request type for the Query/FanToken RPC method 33 | message QueryFanTokenRequest { string denom = 1; } 34 | 35 | // QueryFanTokenResponse is response type for the Query/FanToken RPC method 36 | message QueryFanTokenResponse { 37 | bitsong.fantoken.v1beta1.FanToken fantoken = 1; 38 | } 39 | 40 | // QueryFanTokensRequest is request type for the Query/FanTokens RPC method 41 | message QueryFanTokensRequest { 42 | string authority = 1; 43 | // pagination defines an optional pagination for the request. 44 | cosmos.base.query.v1beta1.PageRequest pagination = 2; 45 | } 46 | 47 | // QueryFanTokensResponse is response type for the Query/FanTokens RPC method 48 | message QueryFanTokensResponse { 49 | repeated bitsong.fantoken.v1beta1.FanToken fantokens = 1; 50 | cosmos.base.query.v1beta1.PageResponse pagination = 2; 51 | } 52 | 53 | // QueryParametersRequest is request type for the Query/Parameters RPC method 54 | message QueryParamsRequest {} 55 | 56 | // QueryParametersResponse is response type for the Query/Parameters RPC method 57 | message QueryParamsResponse { 58 | bitsong.fantoken.v1beta1.Params params = 1 [ (gogoproto.nullable) = false ]; 59 | } -------------------------------------------------------------------------------- /proto/bitsong/smartaccount/v1beta1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.smartaccount.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "bitsong/smartaccount/v1beta1/params.proto"; 6 | import "bitsong/smartaccount/v1beta1/models.proto"; 7 | 8 | option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; 9 | 10 | // AuthenticatorData represents a genesis exported account with Authenticators. 11 | // The address is used as the key, and the account authenticators are stored in 12 | // the authenticators field. 13 | message AuthenticatorData { 14 | // address is an account address, one address can have many authenticators 15 | string address = 1; 16 | 17 | // authenticators are the account's authenticators, these can be multiple 18 | // types including SignatureVerification, AllOfs, CosmWasmAuthenticators, etc 19 | repeated AccountAuthenticator authenticators = 2 20 | [ (gogoproto.nullable) = false ]; 21 | } 22 | 23 | // GenesisState defines the authenticator module's genesis state. 24 | message GenesisState { 25 | // params define the parameters for the authenticator module. 26 | Params params = 1 [ (gogoproto.nullable) = false ]; 27 | 28 | // next_authenticator_id is the next available authenticator ID. 29 | uint64 next_authenticator_id = 2; 30 | 31 | // authenticator_data contains the data for multiple accounts, each with their 32 | // authenticators. 33 | repeated AuthenticatorData authenticator_data = 3 34 | [ (gogoproto.nullable) = false ]; 35 | } 36 | -------------------------------------------------------------------------------- /proto/bitsong/smartaccount/v1beta1/models.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.smartaccount.v1beta1; 3 | 4 | option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; 5 | 6 | // AccountAuthenticator represents a foundational model for all authenticators. 7 | // It provides extensibility by allowing concrete types to interpret and 8 | // validate transactions based on the encapsulated data. 9 | message AccountAuthenticator { 10 | // ID uniquely identifies the authenticator instance. 11 | uint64 id = 1; 12 | 13 | // Type specifies the category of the AccountAuthenticator. 14 | // This type information is essential for differentiating authenticators 15 | // and ensuring precise data retrieval from the storage layer. 16 | string type = 2; 17 | 18 | // Config is a versatile field used in conjunction with the specific type of 19 | // account authenticator to facilitate complex authentication processes. 20 | // The interpretation of this field is overloaded, enabling multiple 21 | // authenticators to utilize it for their respective purposes. 22 | bytes config = 3; 23 | } 24 | -------------------------------------------------------------------------------- /proto/bitsong/smartaccount/v1beta1/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.smartaccount.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; 7 | 8 | // Params defines the parameters for the module. 9 | message Params { 10 | // MaximumUnauthenticatedGas defines the maximum amount of gas that can be 11 | // used to authenticate a transaction in ante handler without having fee payer 12 | // authenticated. 13 | uint64 maximum_unauthenticated_gas = 1 14 | [ (gogoproto.moretags) = "yaml:\"maximum_unauthenticated_gas\"" ]; 15 | 16 | // IsSmartAccountActive defines the state of the authenticator. 17 | // If set to false, the authenticator module will not be used 18 | // and the classic cosmos sdk authentication will be used instead. 19 | bool is_smart_account_active = 2 20 | [ (gogoproto.moretags) = "yaml:\"is_smart_account_active\"" ]; 21 | 22 | // CircuitBreakerControllers defines list of addresses that are allowed to 23 | // set is_smart_account_active without going through governance. 24 | repeated string circuit_breaker_controllers = 3 25 | [ (gogoproto.moretags) = "yaml:\"circuit_breaker_controllers\"" ]; 26 | } 27 | -------------------------------------------------------------------------------- /proto/bitsong/smartaccount/v1beta1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.smartaccount.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/query/v1beta1/pagination.proto"; 7 | import "bitsong/smartaccount/v1beta1/params.proto"; 8 | import "bitsong/smartaccount/v1beta1/models.proto"; 9 | 10 | option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; 11 | 12 | // Query defines the gRPC querier service. 13 | service Query { 14 | // Parameters queries the parameters of the module. 15 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 16 | option (google.api.http).get = "/bitsong/smartaccount/params"; 17 | } 18 | 19 | rpc GetAuthenticator(GetAuthenticatorRequest) 20 | returns (GetAuthenticatorResponse) { 21 | option (google.api.http).get = 22 | "/bitsong/smartaccount/authenticator/{account}/{authenticator_id}"; 23 | } 24 | 25 | rpc GetAuthenticators(GetAuthenticatorsRequest) 26 | returns (GetAuthenticatorsResponse) { 27 | option (google.api.http).get = 28 | "/bitsong/smartaccount/authenticators/{account}"; 29 | } 30 | } 31 | 32 | // QueryParamsRequest is request type for the Query/Params RPC method. 33 | message QueryParamsRequest {} 34 | 35 | // QueryParamsResponse is response type for the Query/Params RPC method. 36 | message QueryParamsResponse { 37 | // params holds all the parameters of this module. 38 | Params params = 1 [ (gogoproto.nullable) = false ]; 39 | } 40 | 41 | // MsgGetAuthenticatorsRequest defines the Msg/GetAuthenticators request type. 42 | message GetAuthenticatorsRequest { string account = 1; } 43 | 44 | // MsgGetAuthenticatorsResponse defines the Msg/GetAuthenticators response type. 45 | message GetAuthenticatorsResponse { 46 | repeated AccountAuthenticator account_authenticators = 1; 47 | } 48 | 49 | // MsgGetAuthenticatorRequest defines the Msg/GetAuthenticator request type. 50 | message GetAuthenticatorRequest { 51 | string account = 1; 52 | uint64 authenticator_id = 2; 53 | } 54 | 55 | // MsgGetAuthenticatorResponse defines the Msg/GetAuthenticator response type. 56 | message GetAuthenticatorResponse { 57 | AccountAuthenticator account_authenticator = 1; 58 | } -------------------------------------------------------------------------------- /proto/bitsong/smartaccount/v1beta1/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package bitsong.smartaccount.v1beta1; 3 | 4 | import "cosmos/msg/v1/msg.proto"; 5 | import "amino/amino.proto"; 6 | 7 | option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; 8 | 9 | // Msg defines the Msg service. 10 | service Msg { 11 | rpc AddAuthenticator(MsgAddAuthenticator) 12 | returns (MsgAddAuthenticatorResponse); 13 | rpc RemoveAuthenticator(MsgRemoveAuthenticator) 14 | returns (MsgRemoveAuthenticatorResponse); 15 | 16 | // SetActiveState sets the active state of the authenticator. 17 | // Primarily used for circuit breaking. 18 | rpc SetActiveState(MsgSetActiveState) returns (MsgSetActiveStateResponse); 19 | } 20 | 21 | // MsgAddAuthenticatorRequest defines the Msg/AddAuthenticator request type. 22 | message MsgAddAuthenticator { 23 | option (amino.name) = "bitsong/smartaccount/add-authenticator"; 24 | option (cosmos.msg.v1.signer) = "sender"; 25 | 26 | string sender = 1; 27 | string authenticator_type = 2; 28 | bytes data = 3; 29 | } 30 | 31 | // MsgAddAuthenticatorResponse defines the Msg/AddAuthenticator response type. 32 | message MsgAddAuthenticatorResponse { bool success = 1; } 33 | 34 | // MsgRemoveAuthenticatorRequest defines the Msg/RemoveAuthenticator request 35 | // type. 36 | message MsgRemoveAuthenticator { 37 | option (amino.name) = "bitsong/smartaccount/remove-authenticator"; 38 | option (cosmos.msg.v1.signer) = "sender"; 39 | 40 | string sender = 1; 41 | uint64 id = 2; 42 | } 43 | 44 | // MsgRemoveAuthenticatorResponse defines the Msg/RemoveAuthenticator response 45 | // type. 46 | message MsgRemoveAuthenticatorResponse { bool success = 1; } 47 | 48 | message MsgSetActiveState { 49 | option (amino.name) = "bitsong/smartaccount/set-active-state"; 50 | option (cosmos.msg.v1.signer) = "sender"; 51 | 52 | string sender = 1; 53 | bool active = 2; 54 | } 55 | 56 | message MsgSetActiveStateResponse {} 57 | 58 | // TxExtension allows for additional authenticator-specific data in 59 | // transactions. 60 | message TxExtension { 61 | // selected_authenticators holds the authenticator_id for the chosen 62 | // authenticator per message. 63 | repeated uint64 selected_authenticators = 1; 64 | } 65 | -------------------------------------------------------------------------------- /proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: .. 5 | opt: plugins=grpc,Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types,Mcosmos/orm/v1alpha1/orm.proto=github.com/cosmos/cosmos-sdk/api/cosmos/orm/v1alpha1 6 | - name: grpc-gateway 7 | out: .. 8 | opt: logtostderr=true,allow_colon_final_segments=true 9 | -------------------------------------------------------------------------------- /proto/buf.gen.pulsar.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | 3 | managed: 4 | enabled: true 5 | go_package_prefix: 6 | default: cosmossdk.io/api 7 | except: 8 | - buf.build/googleapis/googleapis 9 | - buf.build/cosmos/gogo-proto 10 | - buf.build/cosmos/cosmos-proto 11 | plugins: 12 | - name: go-pulsar 13 | out: ../api 14 | opt: paths=source_relative 15 | - name: go-grpc 16 | out: ../api 17 | opt: paths=source_relative 18 | -------------------------------------------------------------------------------- /proto/buf.gen.swagger.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: swagger 4 | out: ../tmp-swagger-gen 5 | opt: logtostderr=true,fqn_for_swagger_name=true,simple_operation_ids=true -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 04467658e59e44bbb22fe568206e1f70 8 | digest: shake256:73a640bd60e0c523b0f8237ff34eab67c45a38b64bbbde1d80224819d272dbf316ac183526bd245f994af6608b025f5130483d0133c5edd385531326b5990466 9 | - remote: buf.build 10 | owner: cosmos 11 | repository: cosmos-sdk 12 | commit: 954f7b05f38440fc8250134b15adec47 13 | digest: shake256:2ab4404fd04a7d1d52df0e2d0f2d477a3d83ffd88d876957bf3fedfd702c8e52833d65b3ce1d89a3c5adf2aab512616b0e4f51d8463f07eda9a8a3317ee3ac54 14 | - remote: buf.build 15 | owner: cosmos 16 | repository: gogo-proto 17 | commit: 88ef6483f90f478fb938c37dde52ece3 18 | digest: shake256:89c45df2aa11e0cff97b0d695436713db3d993d76792e9f8dc1ae90e6ab9a9bec55503d48ceedd6b86069ab07d3041b32001b2bfe0227fa725dd515ff381e5ba 19 | - remote: buf.build 20 | owner: googleapis 21 | repository: googleapis 22 | commit: e93e34f48be043dab55be31b4b47f458 23 | digest: shake256:93dbe51c27606999eef918360df509485a4d272e79aaed6d0016940379a9b06d316fc5228b7b50cca94bb310f34c5fc5955ce7474f655f0d0a224c4121dda3c1 24 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/bitsongofficial/go-bitsong 3 | deps: 4 | - buf.build/cosmos/cosmos-sdk:v0.47.0 5 | - buf.build/cosmos/cosmos-proto 6 | - buf.build/cosmos/gogo-proto 7 | - buf.build/googleapis/googleapis 8 | breaking: 9 | use: 10 | - FILE 11 | lint: 12 | use: 13 | - DEFAULT 14 | - COMMENTS 15 | - FILE_LOWER_SNAKE_CASE 16 | except: 17 | - UNARY_RPC 18 | - COMMENT_FIELD 19 | - SERVICE_SUFFIX 20 | - PACKAGE_VERSION_SUFFIX 21 | - PACKAGE_SAME_GO_PACKAGE 22 | - PACKAGE_SAME_DIRECTORY 23 | - PACKAGE_DIRECTORY_MATCH 24 | - RPC_REQUEST_STANDARD_NAME 25 | ignore: 26 | - tendermint -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Bitsong Scripting Library 2 | 3 | ## Make 4 | Make files allow us to organize make functions into files specific to their purpose. 5 | | CLI | Description | | | | 6 | |---|---|---|---|---| 7 | | `build` | builds `bitsongd` binary | | | | 8 | | `docker` | current docker options | | | | 9 | | `e2e` | current e2e & integration tests | | | | 10 | | `hl` | current heighliner options | | | | 11 | | `localnet` | localBitsong commands | | | | 12 | | `proto` | generate protobuf documentation and files | | | | 13 | -------------------------------------------------------------------------------- /scripts/makefiles/docker.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Docker ### 3 | ############################################################################### 4 | 5 | RUNNER_BASE_IMAGE_DISTROLESS := gcr.io/distroless/static-debian11 6 | RUNNER_BASE_IMAGE_ALPINE := alpine:3.17 7 | RUNNER_BASE_IMAGE_NONROOT := gcr.io/distroless/static-debian11:nonroot 8 | 9 | docker-help: 10 | @echo "docker subcommands" 11 | @echo "" 12 | @echo "Usage:" 13 | @echo " make [command]" 14 | @echo "" 15 | @echo "Available Commands:" 16 | @echo " docker-build Build Docker image" 17 | @echo " docker-build-distroless Build distroless Docker image" 18 | @echo " docker-build-alpine Build alpine Docker image" 19 | @echo " docker-build-nonroot Build nonroot Docker image" 20 | docker: docker-help 21 | 22 | docker-build: 23 | @DOCKER_BUILDKIT=1 docker build \ 24 | -t bitsong:local \ 25 | --build-arg GO_VERSION=$(GO_VERSION) \ 26 | --build-arg RUNNER_IMAGE=$(RUNNER_BASE_IMAGE_DISTROLESS) \ 27 | --build-arg GIT_VERSION=$(VERSION) \ 28 | --build-arg GIT_COMMIT=$(COMMIT) \ 29 | -f Dockerfile . 30 | 31 | docker-build-distroless: docker-build 32 | 33 | docker-build-alpine: 34 | @DOCKER_BUILDKIT=1 docker build \ 35 | -t bitsong:local-alpine \ 36 | --build-arg GO_VERSION=$(GO_VERSION) \ 37 | --build-arg RUNNER_IMAGE=$(RUNNER_BASE_IMAGE_ALPINE) \ 38 | --build-arg GIT_VERSION=$(VERSION) \ 39 | --build-arg GIT_COMMIT=$(COMMIT) \ 40 | -f Dockerfile . 41 | 42 | docker-build-nonroot: 43 | @DOCKER_BUILDKIT=1 docker build \ 44 | -t bitsong:local-nonroot \ 45 | --build-arg GO_VERSION=$(GO_VERSION) \ 46 | --build-arg RUNNER_IMAGE=$(RUNNER_BASE_IMAGE_NONROOT) \ 47 | --build-arg GIT_VERSION=$(VERSION) \ 48 | --build-arg GIT_COMMIT=$(COMMIT) \ 49 | -f Dockerfile . 50 | -------------------------------------------------------------------------------- /scripts/makefiles/e2e.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### e2e interchain test ### 3 | ############################################################################### 4 | 5 | e2e-help: 6 | @echo "e2e subcommands" 7 | @echo "" 8 | @echo "Usage:" 9 | @echo " make [command]" 10 | @echo "" 11 | @echo "Available Commands:" 12 | @echo " e2e-basic Run single node " 13 | @echo " e2e-upgrade Run basic planned upgrade test" 14 | @echo " e2e-pfm Run packet-forward-middleware test " 15 | @echo " e2e-polytone Run polytone test contracts: Run ./scripts/polytone.sh to install wasm blobs." 16 | @echo " e2e-slashing Test slashing actions" 17 | 18 | e2e: e2e-help 19 | 20 | e2e-basic: rm-testcache 21 | cd tests/ict && go test -race -v -run TestBasicBtsgStart . 22 | 23 | e2e-upgrade: rm-testcache 24 | cd tests/ict && go test -race -v -run TestBasicBitsongUpgrade . 25 | 26 | e2e-pfm: rm-testcache 27 | cd tests/ict && go test -race -v -run TestPacketForwardMiddlewareRouter . 28 | 29 | e2e-polytone: rm-testcache 30 | cd tests/ict && go test -race -v -run TestPolytoneOnBitsong . 31 | 32 | e2e-slashing: rm-testcache 33 | cd tests/ict && go test -race -v -run TestBasicBitsongSlashing . 34 | 35 | rm-testcache: 36 | go clean -testcache 37 | 38 | 39 | .PHONY: test-mutation ie2e-upgrade -------------------------------------------------------------------------------- /scripts/makefiles/format.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Formatting ### 3 | ############################################################################### 4 | 5 | 6 | format-help: 7 | @echo "formatting subcommands" 8 | @echo "" 9 | @echo "Usage:" 10 | @echo " make [command]" 11 | @echo "" 12 | @echo "Available Commands:" 13 | @echo " format-tidy run go mod tidy for all go.mod files " 14 | @echo " format-format " 15 | 16 | format: format-help 17 | 18 | format-tidy: 19 | @go mod tidy 20 | @cd ./tests/integration && go mod tidy 21 | @cd ./tests/petri && go mod tidy 22 | @cd ./tests/simapp && go mod tidy 23 | 24 | .PHONY: tidy 25 | 26 | format-format: 27 | @find . -name '*.go' -type f -not -path "*.git*" -not -path "*/mocks/*" -not -name '*.pb.go' -not -name '*.pulsar.go' -not -name '*.gw.go' | xargs go run mvdan.cc/gofumpt -w . 28 | @find . -name '*.go' -type f -not -path "*.git*" -not -path "*/mocks/*" -not -name '*.pb.go' -not -name '*.pulsar.go' -not -name '*.gw.go' | xargs go run github.com/client9/misspell/cmd/misspell -w 29 | @find . -name '*.go' -type f -not -path "*.git*" -not -path "/*mocks/*" -not -name '*.pb.go' -not -name '*.pulsar.go' -not -name '*.gw.go' | xargs go run golang.org/x/tools/cmd/goimports -w -local github.com/bitsongofficial/go-bitsong 30 | 31 | .PHONY: format 32 | -------------------------------------------------------------------------------- /scripts/makefiles/hl.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### heighliner - (used for docker containers) ### 3 | ############################################################################### 4 | .PHONY: heighliner-get heighliner-local-image heighliner 5 | 6 | heighliner-help: 7 | @echo "heighliner subcommands" 8 | @echo "" 9 | @echo "Usage:" 10 | @echo " make heighliner-[command]" 11 | @echo "" 12 | @echo "Available Commands:" 13 | @echo " hl-get Install Heighliner. ensure your PATH env variable is set to run the binary command." 14 | @echo " hl-local-image Create a local image" 15 | @echo " hl-previous-image Create a local image from the previous version from upgrade." 16 | @echo "" 17 | @echo "" 18 | 19 | 20 | hl: heighliner-help 21 | 22 | hl-get: 23 | git clone https://github.com/strangelove-ventures/heighliner.git 24 | cd heighliner && go install 25 | 26 | hl-local-image: 27 | ifeq (,$(shell which heighliner)) 28 | echo 'heighliner' binary not found. Consider running `make hl-get` 29 | else 30 | heighliner build -c bitsong -o bitsongofficial/go-bitsong --local -f ./chains.yaml 31 | # heighliner build -c bitsong -o bitsongofficial/go-bitsong --local -f ./chains.yaml -t v0.18.1 32 | endif -------------------------------------------------------------------------------- /scripts/makefiles/localnet.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Localnet ### 3 | ############################################################################### 4 | # 5 | # Please refer to https://github.com/bitsongofficial/go-bitsong/blob/main/e2e/localbitsong/README.md for detailed 6 | # usage of localnet. 7 | 8 | localnet-help: 9 | @echo "build subcommands" 10 | @echo "" 11 | @echo "Usage:" 12 | @echo " make [command]" 13 | @echo "" 14 | @echo "Available Commands:" 15 | @echo "localnet-start Start localnet" 16 | @echo "localnet-stop Stop localnet" 17 | @echo "test-docker-push Push testnet docker image" 18 | 19 | localnet: localnet-help 20 | 21 | localnet-keys: 22 | . e2e/localbitsong/scripts/add_keys.sh 23 | 24 | localnet-init: localnet-clean localnet-build 25 | 26 | localnet-build: 27 | @DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker compose -f e2e/localbitsong/docker-compose.yml build 28 | 29 | localnet-start: 30 | @STATE="" docker compose -f e2e/localbitsong/docker-compose.yml up 31 | 32 | localnet-startd: 33 | @STATE="" docker compose -f e2e/localbitsong/docker-compose.yml up -d 34 | 35 | localnet-stop: 36 | @STATE="" docker compose -f e2e/localbitsong/docker-compose.yml down 37 | 38 | localnet-clean: 39 | @rm -rfI $(HOME)/.bitsongd-local/ -------------------------------------------------------------------------------- /scripts/makefiles/proto.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Proto ### 3 | ############################################################################### 4 | 5 | protoVer=0.15.1 6 | protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) 7 | protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) 8 | SWAGGER_DIR=./swagger-proto 9 | THIRD_PARTY_DIR=$(SWAGGER_DIR)/third_party 10 | 11 | proto-help: 12 | @echo "proto subcommands" 13 | @echo "" 14 | @echo "Usage:" 15 | @echo " make proto-[command]" 16 | @echo "" 17 | @echo "Available Commands:" 18 | @echo " proto-all Run proto-format and proto-gen" 19 | @echo " proto-check-breaking Check breaking instances" 20 | @echo " proto-gen Generate Protobuf files" 21 | @echo " proto-pulsar-gen Generate Protobuf files" 22 | @echo " proto-format Format Protobuf files" 23 | @echo " proto-lint Lint Protobuf files" 24 | @echo " proto-image-build Build the protobuf Docker image" 25 | @echo " proto-image-push Push the protobuf Docker image" 26 | @echo " proto-docs Create Swagger API docs" 27 | 28 | proto: proto-help 29 | 30 | proto-all: proto-format proto-gen 31 | 32 | proto-gen: 33 | @echo "Generating Protobuf files" 34 | @$(protoImage) sh ./scripts/protocgen.sh 35 | 36 | proto-pulsar-gen: 37 | @echo "Generating Dep-Inj Protobuf files" 38 | @$(protoImage) sh ./scripts/protocgen-pulsar.sh 39 | 40 | # linux only 41 | proto-format: 42 | @echo "Formatting Protobuf files" 43 | @$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace tendermintdev/docker-build-proto \ 44 | find ./proto -name "*.proto" -exec clang-format -i {} \; 45 | 46 | proto-update-deps: 47 | @echo "Updating Protobuf dependencies" 48 | @$(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf mod update 49 | 50 | proto-lint: 51 | @$(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf lint --error-format=json 52 | 53 | proto-check-breaking: 54 | @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main 55 | 56 | proto-docs: 57 | @echo 58 | @echo "=========== Generate Message ============" 59 | @echo 60 | sh ./scripts/protoc-swagger-gen.sh 61 | 62 | @echo 63 | @echo "=========== Generate Complete ============" 64 | @echo 65 | .PHONY: docs -------------------------------------------------------------------------------- /scripts/makefiles/tests.mk: -------------------------------------------------------------------------------- 1 | 2 | 3 | test-help: 4 | @echo "test subcommands" 5 | @echo "" 6 | @echo "Usage:" 7 | @echo " make [command]" 8 | @echo "" 9 | @echo "Available Commands:" 10 | @echo " e2e View e2e tests available to run" 11 | @echo " test-all Run all tests" 12 | @echo " test-unit Run unit tests" 13 | @echo " test-benchmark Run benchmark tests" 14 | @echo " test-cover Run coverage tests" 15 | @echo " test-race Run race tests" 16 | @echo " test-race Run race tests" 17 | 18 | test: test-help 19 | test-all: test-race test-cover test-unit 20 | 21 | test-unit: 22 | @VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' -ldflags '$(ldflags)' ${PACKAGES_UNITTEST} 23 | 24 | test-race: 25 | @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' ./... 26 | 27 | test-cover: 28 | @go test -mod=readonly -timeout 30m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' ./... 29 | 30 | test-benchmark: 31 | @go test -mod=readonly -bench=. ./... 32 | 33 | # include simulations 34 | # include sims.mk -------------------------------------------------------------------------------- /scripts/polytone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BASE_URL="https://github.com/DA0-DA0/polytone/releases/download/v1.1.0/" 3 | # Directory to store WASM files 4 | POLYTONE_WASM_DIR="tests/ict/contracts/" 5 | mkdir -p "$POLYTONE_WASM_DIR" 6 | 7 | POLYTONE_CONTRACTS=( 8 | "polytone_listener.wasm" 9 | "polytone_note.wasm" 10 | "polytone_proxy.wasm" 11 | "polytone_voice.wasm" 12 | "polytone_tester.wasm" 13 | ) 14 | 15 | # Download each WASM file 16 | for CONTRACT in "${POLYTONE_CONTRACTS[@]}"; do 17 | FILE_URL="$BASE_URL$CONTRACT" 18 | FILE_PATH="$POLYTONE_WASM_DIR/$CONTRACT" 19 | 20 | # Construct the curl command 21 | CURL_CMD="curl -v -L -o '$FILE_PATH' '$FILE_URL'" 22 | 23 | echo "### Attempting Download with curl ###" 24 | echo "$CURL_CMD" 25 | echo "--- Output will follow ---" 26 | 27 | # Attempt the download 28 | eval "$CURL_CMD" 29 | 30 | # Check the file size after download 31 | FILE_SIZE=$(stat -c%s "$FILE_PATH" 2>/dev/null) 32 | 33 | if [ $? -eq 0 ] && [ $FILE_SIZE -gt 0 ]; then 34 | echo "$CONTRACT downloaded successfully (Size: $FILE_SIZE bytes)." 35 | else 36 | echo "Failed to download $CONTRACT or the file is empty. **Please try copying and running the above curl command manually to troubleshoot.**" 37 | fi 38 | 39 | # Empty line for readability 40 | echo 41 | done -------------------------------------------------------------------------------- /scripts/prep-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 1. Build reproducible images 4 | # echo "Building reproducible images..." 5 | # make build-reproducible 6 | 7 | # 2. Create tar.gz files of binaries 8 | echo "Creating tar.gz files of binaries..." 9 | tar -czvf build/bitsongd-linux-amd64.tar.gz build/bitsongd-linux-amd64 10 | tar -czvf build/bitsongd-linux-arm64.tar.gz build/bitsongd-linux-arm64 11 | 12 | # 3. Calculate sha256sum for all images into checksum.txt in ./build 13 | echo "Calculating sha256sum for all images..." 14 | 15 | sha256sum build/bitsongd-linux-amd64 > build/checksum.txt 16 | sha256sum build/bitsongd-linux-arm64 >> build/checksum.txt 17 | sha256sum build/bitsongd-linux-amd64.tar.gz >> build/checksum.txt 18 | sha256sum build/bitsongd-linux-arm64.tar.gz >> build/checksum.txt 19 | 20 | echo "SHA256 checksums have been saved to build/checksum.txt." -------------------------------------------------------------------------------- /scripts/protocgen-any.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script generates a custom wrapper for google.protobuf.Any in 4 | # codec/types/any.pb.go with a custom generated struct that lives in 5 | # codec/types/any.go 6 | 7 | set -eo pipefail 8 | 9 | go install github.com/gogo/protobuf/protoc-gen-gogotypes 10 | 11 | buf protoc -I "third_party/proto" --gogotypes_out=./codec/types third_party/proto/google/protobuf/any.proto 12 | mv codec/types/google/protobuf/any.pb.go codec/types 13 | rm -rf codec/types/third_party 14 | 15 | # This removes the call to RegisterType in the custom generated Any wrapper 16 | # so that only the Any type provided by gogo protobuf is registered in the 17 | # global gogo protobuf type registry, which we aren't actually using 18 | sed '/proto\.RegisterType/d' codec/types/any.pb.go > tmp && mv tmp codec/types/any.pb.go -------------------------------------------------------------------------------- /scripts/protocgen-pulsar.sh: -------------------------------------------------------------------------------- 1 | # this script is for generating protobuf files for the new google.golang.org/protobuf API 2 | 3 | set -eo pipefail 4 | 5 | protoc_install_gopulsar() { 6 | go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest 7 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 8 | } 9 | 10 | protoc_install_gopulsar 11 | 12 | echo "Cleaning API directory" 13 | (cd proto; find ./ -type f \( -iname \*.pulsar.go -o -iname \*.pb.go -o -iname \*.cosmos_orm.go -o -iname \*.pb.gw.go \) -delete; find . -empty -type d -delete; cd ..) 14 | 15 | echo "Generating API module" 16 | (cd proto; buf generate --template buf.gen.pulsar.yaml ) # --exclude-path connect/service 17 | 18 | # echo "fixing types.pulsar.go" 19 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/types/v2/currency_pair.pulsar.go && rm ./api/connect/types/v2/currency_pair.pulsar.go.bak 20 | # sed -i.bak 's|cosmossdk.io/api/connect/oracle/v2|github.com/skip-mev/connect/v2/api/connect/oracle/v2|g' ./api/connect/types/v2/currency_pair.pulsar.go && rm ./api/connect/types/v2/currency_pair.pulsar.go.bak 21 | 22 | # echo "fixing oracle.pulsar.go" 23 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/oracle/v2/query.pulsar.go && rm ./api/connect/oracle/v2/query.pulsar.go.bak 24 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/oracle/v2/tx.pulsar.go && rm ./api/connect/oracle/v2/tx.pulsar.go.bak 25 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/oracle/v2/genesis.pulsar.go && rm ./api/connect/oracle/v2/genesis.pulsar.go.bak 26 | 27 | # echo "fixing market.pulsar.go" 28 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/marketmap/v2/market.pulsar.go && rm ./api/connect/marketmap/v2/market.pulsar.go.bak 29 | # sed -i.bak 's|cosmossdk.io/api/connect/types/v2|github.com/skip-mev/connect/v2/api/connect/types/v2|g' ./api/connect/marketmap/v2/query.pulsar.go && rm ./api/connect/marketmap/v2/query.pulsar.go.bak 30 | # sed -i.bak 's|cosmossdk.io/api/connect/oracle/v2|github.com/skip-mev/connect/v2/api/connect/oracle/v2|g' ./api/connect/marketmap/v2/market.pulsar.go && rm ./api/connect/marketmap/v2/market.pulsar.go.bak 31 | -------------------------------------------------------------------------------- /scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | echo "Generating Protocol Buffer code..." 5 | cd proto 6 | proto_dirs=$(find ./bitsong -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 7 | for dir in $proto_dirs; do 8 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 9 | if grep go_package $file &> /dev/null ; then 10 | buf generate --template buf.gen.gogo.yaml $file 11 | fi 12 | done 13 | done 14 | 15 | cd .. 16 | 17 | # move proto files to the right places 18 | cp -r github.com/bitsongofficial/go-bitsong/* ./ 19 | rm -rf github.com 20 | 21 | # go mod tidy --compat=1.20 22 | -------------------------------------------------------------------------------- /server/types.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | type ValidatorInfo struct { 4 | Val string `json:"val"` 5 | NumDels int `json:"num_dels"` 6 | NumTokens int `json:"num_tokens"` 7 | Jailed bool `json:"jailed"` 8 | } 9 | -------------------------------------------------------------------------------- /server/util.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path/filepath" 7 | 8 | dbm "github.com/cosmos/cosmos-db" 9 | "github.com/cosmos/cosmos-sdk/server/types" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { 14 | dataDir := filepath.Join(rootDir, "data") 15 | return dbm.NewDB("application", backendType, dataDir) 16 | } 17 | 18 | func openTraceWriter(traceWriterFile string) (w io.WriteCloser, err error) { 19 | if traceWriterFile == "" { 20 | return 21 | } 22 | return os.OpenFile( 23 | traceWriterFile, 24 | os.O_WRONLY|os.O_APPEND|os.O_CREATE, 25 | 0o666, 26 | ) 27 | } 28 | 29 | // AddTestnetCreatorCommand allows chains to create a testnet from the state existing in their node's data directory. 30 | func AddTestnetCreatorCommand(rootCmd *cobra.Command, appCreator types.AppCreator, addStartFlags types.ModuleInitFlags) { 31 | testnetCreateCmd := InPlaceTestnetCreator(appCreator) 32 | addStartFlags(testnetCreateCmd) 33 | rootCmd.AddCommand(testnetCreateCmd) 34 | } 35 | -------------------------------------------------------------------------------- /swagger/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "BitSong - gRPC Gateway docs", 5 | "description": "A REST interface for state queries, legacy transactions", 6 | "version": "0.11.0" 7 | }, 8 | "apis": [ 9 | { 10 | "url": "./tmp-swagger-gen/bitsong/fantoken/v1beta1/query.swagger.json", 11 | "dereference": { 12 | "circular": "ignore" 13 | }, 14 | "operationIds": { 15 | "rename": { 16 | "Params": "FantokenParams" 17 | } 18 | } 19 | }, 20 | { 21 | "url": "./swagger/swagger-sdk.yaml", 22 | "dereference": { 23 | "circular": "ignore" 24 | }, 25 | "tags": { 26 | "rename": { 27 | "Gaia REST": "CometBft RPC" 28 | } 29 | }, 30 | "operationIds": { 31 | "rename": { 32 | "UpgradedConsensusState": "sdk/UpgradedConsensusState" 33 | } 34 | } 35 | }, 36 | { 37 | "url": "./swagger/swagger-ibc.yaml", 38 | "dereference": { 39 | "circular": "ignore" 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /swagger/swagger-ui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/swagger/swagger-ui/favicon-16x16.png -------------------------------------------------------------------------------- /swagger/swagger-ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/swagger/swagger-ui/favicon-32x32.png -------------------------------------------------------------------------------- /swagger/swagger-ui/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /swagger/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /swagger/swagger-ui/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "swagger.yaml", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /swagger/swagger-ui/swagger-initializer.js.bak: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "swagger.yaml", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Bitsong E2E Testing 2 | 3 | ## Current Testing Libraries 4 | | Library | Description | Language | Version | Built By | 5 | |---|---|---|---|---| 6 | | `cw-orch` | Cw-Orchestration scripting library | Rust | `v0.24.*` | Abstract | 7 | | `ictest` | Dockerized live network environments | Go | `v7.*` | Strangelove | 8 | | `localbitsong` | local instance of Bitsong | Bash | `v0.0.1` | Osmosis-labs | 9 | | `simtests` | Simulated network state & env | Go | `v0.0.1` | Cosmos | 10 | -------------------------------------------------------------------------------- /tests/cw-orch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Cw-Orchestrator end-to-end testing library for Bitsong." 3 | license = "Apache-2.0" 4 | name = "bitsong-e2e-cw-orchestrator" 5 | version = "0.0.1" 6 | 7 | [dependencies] 8 | cosmwasm-std = "2.1.4" 9 | cw-orch = { version = "0.27.0", features = ["daemon"] } 10 | cw-orch-interchain = { version = "0.8.1", features = ["daemon"] } 11 | cw-orch-proto = "0.9.0" 12 | cw-utils = "2.0.0" 13 | cw2 = "2.0.0" 14 | 15 | abstract-adapter = { version = "0.24.1", features = ["test-utils"] } 16 | abstract-app = { version = "0.24.1", features = ["test-utils"] } 17 | abstract-client = { version = "0.24.1" } 18 | abstract-interface = { version = "0.24.1", features = ["interchain"] } 19 | abstract-sdk = "0.24.1" 20 | abstract-std = " 0.24.1" 21 | abstract-testing = "0.24.1" 22 | # abstract-scripts = { path = "./scripts" } 23 | 24 | anyhow = "1.0.94" 25 | dotenv = "0.15.0" 26 | env_logger = "0.11.3" 27 | log = "0.4.14" 28 | tokio = "1.42.0" 29 | base64 = "0.22.1" 30 | ibc-relayer-types = "0.29" 31 | 32 | 33 | cosmwasm-schema = "2.0.0" 34 | cw-controllers = "2.0.0" 35 | cw-storage-plus = "2.0.0" 36 | # cw-orch-polytone = { package = "abstract-cw-orch-polytone", version = "5.0.0" } 37 | # polytone = "0.25.0" 38 | thiserror = "2.0.4" 39 | # Testing contract 40 | counter-contract = { version = "0.27.0" } # Use tag if breaks 41 | 42 | [profile.dev] 43 | debug = true 44 | 45 | [profile.release] 46 | debug = false 47 | opt-level = 3 -------------------------------------------------------------------------------- /tests/cw-orch/README.md: -------------------------------------------------------------------------------- 1 | # Cw-Orchestrator Scripts & Test Library 2 | 3 | 4 | ## Requirements 5 | - Cargo: https://doc.rust-lang.org/cargo/ 6 | - Starship: https://docs.cosmology.zone/starship#quick-start-guide 7 | 8 | 9 | ## Tests 10 | | Name | CLI | description | | | 11 | |---|---|---|---|---| 12 | | iba | `cargo run -- --bin iba` | e2e for CosmWasm ICA callbacks, by creating an interchain bitsong account. | 13 | | | | | | | 14 | | | | | | | 15 | 16 | 17 | 18 | ## Scripts 19 | | Name | CLI | description | | | 20 | |---|---|---|---|---| 21 | | | | | | | 22 | | | | | | | 23 | 24 | -------------------------------------------------------------------------------- /tests/cw-orch/src/bin/grpc.rs: -------------------------------------------------------------------------------- 1 | extern crate abstract_client; 2 | extern crate abstract_interface; 3 | extern crate abstract_std; 4 | extern crate anyhow; 5 | extern crate bitsong_e2e_cw_orchestrator; 6 | extern crate cosmwasm_std; 7 | extern crate cw_orch; 8 | extern crate cw_orch_interchain; 9 | extern crate cw_orch_proto; 10 | 11 | use anyhow::Result as AnyResult; 12 | 13 | // TODO: 14 | pub fn test_single_node_grpc_sanity() -> AnyResult<()> { 15 | // setup single localbitsong instance and query grpc 16 | // perform all grpc requests from modules available, including ibc queries with grpc 17 | Ok(()) 18 | } 19 | 20 | pub fn main() { 21 | if let Err(e) = test_single_node_grpc_sanity() { 22 | eprintln!("Error running gRPC sanity test: {}", e); 23 | std::process::exit(1); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/cw-orch/src/bin/slashing.rs: -------------------------------------------------------------------------------- 1 | extern crate abstract_client; 2 | extern crate abstract_interface; 3 | extern crate abstract_std; 4 | extern crate anyhow; 5 | extern crate bitsong_e2e_cw_orchestrator; 6 | extern crate cosmwasm_std; 7 | extern crate cw_orch; 8 | extern crate cw_orch_interchain; 9 | extern crate cw_orch_proto; 10 | 11 | use anyhow::Result as AnyResult; 12 | 13 | // TODO: 14 | pub fn test_slashing_sanity() -> AnyResult<()> { 15 | // setup multiple nodes and validators 16 | 17 | // setup delegation structure 18 | 19 | // have delegator slash, confirm reward is accurate 20 | 21 | Ok(()) 22 | } 23 | 24 | pub fn main() { 25 | test_slashing_sanity().unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/ict/ci.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | // GetDockerImageInfo returns the appropriate repo and branch version string for integration with the CI pipeline. 9 | // The remote runner sets the BRANCH_CI env var. If present, interchaintest will use the docker image pushed up to the repo. 10 | // If testing locally, user should run `make hl` or 'make docker' to view options to build docker images tagged with local. 11 | func GetDockerImageInfo() (repo, version string) { 12 | branchVersion, found := os.LookupEnv("BRANCH_CI") 13 | repo = BitsongE2eRepo 14 | if !found { 15 | // make local-image 16 | repo = "bitsong" 17 | branchVersion = "local" 18 | } 19 | 20 | // github converts / to - for pushed docker images 21 | branchVersion = strings.ReplaceAll(branchVersion, "/", "-") 22 | return repo, branchVersion 23 | } 24 | -------------------------------------------------------------------------------- /tests/ict/contracts/cw_template.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/cw_template.wasm -------------------------------------------------------------------------------- /tests/ict/contracts/polytone_listener.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/polytone_listener.wasm -------------------------------------------------------------------------------- /tests/ict/contracts/polytone_note.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/polytone_note.wasm -------------------------------------------------------------------------------- /tests/ict/contracts/polytone_proxy.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/polytone_proxy.wasm -------------------------------------------------------------------------------- /tests/ict/contracts/polytone_tester.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/polytone_tester.wasm -------------------------------------------------------------------------------- /tests/ict/contracts/polytone_voice.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/tests/ict/contracts/polytone_voice.wasm -------------------------------------------------------------------------------- /tests/ict/helpers/common.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import "testing" 4 | 5 | func debugOutput(t *testing.T, stdout string) { 6 | if true { 7 | t.Log(stdout) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/ict/helpers/types.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | type GetCountResponse struct { 4 | // {"data":{"count":0}} 5 | Data *GetCountObj `json:"data"` 6 | } 7 | 8 | type GetCountObj struct { 9 | Count int64 `json:"count"` 10 | } 11 | -------------------------------------------------------------------------------- /tests/ict/utils.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "context" 5 | 6 | "cosmossdk.io/math" 7 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 8 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 9 | ) 10 | 11 | func sendTokens(ctx context.Context, chain *cosmos.CosmosChain, from, to ibc.Wallet, token string, amount int64) (ibc.WalletAmount, error) { 12 | if token == "" { 13 | token = chain.Config().Denom 14 | } 15 | 16 | sendAmt := ibc.WalletAmount{ 17 | Address: to.FormattedAddress(), 18 | Denom: token, 19 | Amount: math.NewInt(amount), 20 | } 21 | err := chain.SendFunds(ctx, from.KeyName(), sendAmt) 22 | return sendAmt, err 23 | } 24 | -------------------------------------------------------------------------------- /tests/localbitsong/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "4" 2 | 3 | services: 4 | bitsongd: 5 | image: local:bitsong 6 | build: 7 | context: ../../ 8 | dockerfile: Dockerfile 9 | args: 10 | RUNNER_IMAGE: golang:1.23.0-alpine3.21 11 | GO_VERSION: "1.23.0" 12 | volumes: 13 | - ./scripts/setup.sh:/bitsong/setup.sh 14 | - $HOME/.bitsongd-local/:/bitsongd/.bitsongd/ 15 | entrypoint: 16 | - /bitsong/setup.sh 17 | command: 18 | - $STATE 19 | ports: 20 | - 26657:26657 21 | - 1317:1317 22 | - 9090:9090 23 | - 9091:9091 24 | - 6060:6060 25 | - 9092:9092 26 | -------------------------------------------------------------------------------- /tests/localbitsong/scripts/add_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" | bitsongd keys add val --recover --keyring-backend test 4 | echo "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" | bitsongd keys add lo-test1 --recover --keyring-backend test 5 | echo "quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty" | bitsongd keys add lo-test2 --recover --keyring-backend test 6 | echo "symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb" | bitsongd keys add lo-test3 --recover --keyring-backend test 7 | echo "bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty" | bitsongd keys add lo-test4 --recover --keyring-backend test 8 | echo "second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal" | bitsongd keys add lo-test5 --recover --keyring-backend test 9 | echo "spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick" | bitsongd keys add lo-test6 --recover --keyring-backend test 10 | echo "noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate" | bitsongd keys add lo-test7 --recover --keyring-backend test 11 | echo "cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride" | bitsongd keys add lo-test8 --recover --keyring-backend test 12 | echo "index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred" | bitsongd keys add lo-test9 --recover --keyring-backend test 13 | echo "prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder" | bitsongd keys add lo-test10 --recover --keyring-backend test 14 | -------------------------------------------------------------------------------- /third_party/proto/cosmos/base/query/v1beta1/pagination.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.base.query.v1beta1; 3 | 4 | option go_package = "github.com/cosmos/cosmos-sdk/types/query"; 5 | 6 | // PageRequest is to be embedded in gRPC request messages for efficient 7 | // pagination. Ex: 8 | // 9 | // message SomeRequest { 10 | // Foo some_parameter = 1; 11 | // PageRequest pagination = 2; 12 | // } 13 | message PageRequest { 14 | // key is a value returned in PageResponse.next_key to begin 15 | // querying the next page most efficiently. Only one of offset or key 16 | // should be set. 17 | bytes key = 1; 18 | 19 | // offset is a numeric offset that can be used when key is unavailable. 20 | // It is less efficient than using key. Only one of offset or key should 21 | // be set. 22 | uint64 offset = 2; 23 | 24 | // limit is the total number of results to be returned in the result page. 25 | // If left empty it will default to a value to be set by each app. 26 | uint64 limit = 3; 27 | 28 | // count_total is set to true to indicate that the result set should include 29 | // a count of the total number of items available for pagination in UIs. 30 | // count_total is only respected when offset is used. It is ignored when key 31 | // is set. 32 | bool count_total = 4; 33 | 34 | // reverse is set to true if results are to be returned in the descending order. 35 | // 36 | // Since: cosmos-sdk 0.43 37 | bool reverse = 5; 38 | } 39 | 40 | // PageResponse is to be embedded in gRPC response messages where the 41 | // corresponding request message has used PageRequest. 42 | // 43 | // message SomeResponse { 44 | // repeated Bar results = 1; 45 | // PageResponse page = 2; 46 | // } 47 | message PageResponse { 48 | // next_key is the key to be passed to PageRequest.key to 49 | // query the next page most efficiently 50 | bytes next_key = 1; 51 | 52 | // total is total number of results available if PageRequest.count_total 53 | // was set, its value is undefined otherwise 54 | uint64 total = 2; 55 | } 56 | -------------------------------------------------------------------------------- /third_party/proto/cosmos/base/v1beta1/coin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.base.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/cosmos/cosmos-sdk/types"; 7 | option (gogoproto.goproto_stringer_all) = false; 8 | option (gogoproto.stringer_all) = false; 9 | 10 | // Coin defines a token with a denomination and an amount. 11 | // 12 | // NOTE: The amount field is an Int which implements the custom method 13 | // signatures required by gogoproto. 14 | message Coin { 15 | option (gogoproto.equal) = true; 16 | 17 | string denom = 1; 18 | string amount = 2 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; 19 | } 20 | 21 | // DecCoin defines a token with a denomination and a decimal amount. 22 | // 23 | // NOTE: The amount field is an Dec which implements the custom method 24 | // signatures required by gogoproto. 25 | message DecCoin { 26 | option (gogoproto.equal) = true; 27 | 28 | string denom = 1; 29 | string amount = 2 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; 30 | } 31 | 32 | // IntProto defines a Protobuf wrapper around an Int object. 33 | message IntProto { 34 | string int = 1 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; 35 | } 36 | 37 | // DecProto defines a Protobuf wrapper around a Dec object. 38 | message DecProto { 39 | string dec = 1 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; 40 | } 41 | -------------------------------------------------------------------------------- /third_party/proto/cosmos/upgrade/v1beta1/upgrade.proto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/third_party/proto/cosmos/upgrade/v1beta1/upgrade.proto -------------------------------------------------------------------------------- /third_party/proto/cosmos_proto/cosmos.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos_proto; 3 | 4 | import "google/protobuf/descriptor.proto"; 5 | 6 | option go_package = "github.com/regen-network/cosmos-proto"; 7 | 8 | extend google.protobuf.MessageOptions { 9 | string interface_type = 93001; 10 | 11 | string implements_interface = 93002; 12 | } 13 | 14 | extend google.protobuf.FieldOptions { 15 | string accepts_interface = 93001; 16 | } 17 | -------------------------------------------------------------------------------- /third_party/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/crypto/keys.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/crypto"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | // PublicKey defines the keys available for use with Tendermint Validators 9 | message PublicKey { 10 | option (gogoproto.compare) = true; 11 | option (gogoproto.equal) = true; 12 | 13 | oneof sum { 14 | bytes ed25519 = 1; 15 | bytes secp256k1 = 2; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/crypto/proof.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/crypto"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | message Proof { 9 | int64 total = 1; 10 | int64 index = 2; 11 | bytes leaf_hash = 3; 12 | repeated bytes aunts = 4; 13 | } 14 | 15 | message ValueOp { 16 | // Encoded in ProofOp.Key. 17 | bytes key = 1; 18 | 19 | // To encode in ProofOp.Data 20 | Proof proof = 2; 21 | } 22 | 23 | message DominoOp { 24 | string key = 1; 25 | string input = 2; 26 | string output = 3; 27 | } 28 | 29 | // ProofOp defines an operation used for calculating Merkle root 30 | // The data could be arbitrary format, providing nessecary data 31 | // for example neighbouring node hash 32 | message ProofOp { 33 | string type = 1; 34 | bytes key = 2; 35 | bytes data = 3; 36 | } 37 | 38 | // ProofOps is Merkle proof defined by the list of ProofOps 39 | message ProofOps { 40 | repeated ProofOp ops = 1 [(gogoproto.nullable) = false]; 41 | } 42 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/libs/bits/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.libs.bits; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/libs/bits"; 5 | 6 | message BitArray { 7 | int64 bits = 1; 8 | repeated uint64 elems = 2; 9 | } 10 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/evidence.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "google/protobuf/timestamp.proto"; 8 | import "tendermint/types/types.proto"; 9 | import "tendermint/types/validator.proto"; 10 | 11 | message Evidence { 12 | oneof sum { 13 | DuplicateVoteEvidence duplicate_vote_evidence = 1; 14 | LightClientAttackEvidence light_client_attack_evidence = 2; 15 | } 16 | } 17 | 18 | // DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. 19 | message DuplicateVoteEvidence { 20 | tendermint.types.Vote vote_a = 1; 21 | tendermint.types.Vote vote_b = 2; 22 | int64 total_voting_power = 3; 23 | int64 validator_power = 4; 24 | google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; 25 | } 26 | 27 | // LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. 28 | message LightClientAttackEvidence { 29 | tendermint.types.LightBlock conflicting_block = 1; 30 | int64 common_height = 2; 31 | repeated tendermint.types.Validator byzantine_validators = 3; 32 | int64 total_voting_power = 4; 33 | google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; 34 | } 35 | 36 | message EvidenceList { 37 | repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; 38 | } 39 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "google/protobuf/duration.proto"; 8 | 9 | option (gogoproto.equal_all) = true; 10 | 11 | // ConsensusParams contains consensus critical parameters that determine the 12 | // validity of blocks. 13 | message ConsensusParams { 14 | BlockParams block = 1 [(gogoproto.nullable) = false]; 15 | EvidenceParams evidence = 2 [(gogoproto.nullable) = false]; 16 | ValidatorParams validator = 3 [(gogoproto.nullable) = false]; 17 | VersionParams version = 4 [(gogoproto.nullable) = false]; 18 | } 19 | 20 | // BlockParams contains limits on the block size. 21 | message BlockParams { 22 | // Max block size, in bytes. 23 | // Note: must be greater than 0 24 | int64 max_bytes = 1; 25 | // Max gas per block. 26 | // Note: must be greater or equal to -1 27 | int64 max_gas = 2; 28 | // Minimum time increment between consecutive blocks (in milliseconds) If the 29 | // block header timestamp is ahead of the system clock, decrease this value. 30 | // 31 | // Not exposed to the application. 32 | int64 time_iota_ms = 3; 33 | } 34 | 35 | // EvidenceParams determine how we handle evidence of malfeasance. 36 | message EvidenceParams { 37 | // Max age of evidence, in blocks. 38 | // 39 | // The basic formula for calculating this is: MaxAgeDuration / {average block 40 | // time}. 41 | int64 max_age_num_blocks = 1; 42 | 43 | // Max age of evidence, in time. 44 | // 45 | // It should correspond with an app's "unbonding period" or other similar 46 | // mechanism for handling [Nothing-At-Stake 47 | // attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed). 48 | google.protobuf.Duration max_age_duration = 2 49 | [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; 50 | 51 | // This sets the maximum size of total evidence in bytes that can be committed in a single block. 52 | // and should fall comfortably under the max block bytes. 53 | // Default is 1048576 or 1MB 54 | int64 max_bytes = 3; 55 | } 56 | 57 | // ValidatorParams restrict the public key types validators can use. 58 | // NOTE: uses ABCI pubkey naming, not Amino names. 59 | message ValidatorParams { 60 | option (gogoproto.populate) = true; 61 | option (gogoproto.equal) = true; 62 | 63 | repeated string pub_key_types = 1; 64 | } 65 | 66 | // VersionParams contains the ABCI application version. 67 | message VersionParams { 68 | option (gogoproto.populate) = true; 69 | option (gogoproto.equal) = true; 70 | 71 | uint64 app_version = 1; 72 | } 73 | 74 | // HashedParams is a subset of ConsensusParams. 75 | // 76 | // It is hashed into the Header.ConsensusHash. 77 | message HashedParams { 78 | int64 block_max_bytes = 1; 79 | int64 block_max_gas = 2; 80 | } 81 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/validator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "tendermint/crypto/keys.proto"; 8 | 9 | message ValidatorSet { 10 | repeated Validator validators = 1; 11 | Validator proposer = 2; 12 | int64 total_voting_power = 3; 13 | } 14 | 15 | message Validator { 16 | bytes address = 1; 17 | tendermint.crypto.PublicKey pub_key = 2 [(gogoproto.nullable) = false]; 18 | int64 voting_power = 3; 19 | int64 proposer_priority = 4; 20 | } 21 | 22 | message SimpleValidator { 23 | tendermint.crypto.PublicKey pub_key = 1; 24 | int64 voting_power = 2; 25 | } 26 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/version/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.version; 3 | 4 | option go_package = "github.com/cometbft/cometbft/proto/tendermint/version"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | // App includes the protocol and software version for the application. 9 | // This information is included in ResponseInfo. The App.Protocol can be 10 | // updated in ResponseEndBlock. 11 | message App { 12 | uint64 protocol = 1; 13 | string software = 2; 14 | } 15 | 16 | // Consensus captures the consensus rules for processing a block in the blockchain, 17 | // including all blockchain data structures and the rules of the application's 18 | // state transition machine. 19 | message Consensus { 20 | option (gogoproto.equal) = true; 21 | 22 | uint64 block = 1; 23 | uint64 app = 2; 24 | } 25 | -------------------------------------------------------------------------------- /x/README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # List of Modules 6 | 7 | Here are some production-grade modules that can be used in BitSong applications, along with their respective documentation: 8 | 9 | - [fantoken](fantoken/spec/README.md) - Fantoken managing functionalities. 10 | - [cadence](cadence/spec/README.md) - Cadence automates smart contract actions for each block. 11 | - [smart-accounts](smart-accounts/spec/README.md) - Smart accounts allow authentication of actions for accounts in programmable manners. 12 | -------------------------------------------------------------------------------- /x/cadence/README.md: -------------------------------------------------------------------------------- 1 | # Cadence 2 | 3 | This module allows smart contracts to execute logic at the end of every block without an external bot. 4 | 5 | [Cadence Spec](./spec/README.md) 6 | -------------------------------------------------------------------------------- /x/cadence/genesis.go: -------------------------------------------------------------------------------- 1 | package cadence 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | "github.com/bitsongofficial/go-bitsong/x/cadence/keeper" 10 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 11 | ) 12 | 13 | // NewGenesisState - Create a new genesis state 14 | func NewGenesisState(params types.Params) *types.GenesisState { 15 | return &types.GenesisState{ 16 | Params: params, 17 | } 18 | } 19 | 20 | // DefaultGenesisState - Return a default genesis state 21 | func DefaultGenesisState() *types.GenesisState { 22 | return NewGenesisState(types.DefaultParams()) 23 | } 24 | 25 | // GetGenesisStateFromAppState returns x/auth GenesisState given raw application 26 | // genesis state. 27 | func GetGenesisStateFromAppState(cdc codec.Codec, appState map[string]json.RawMessage) *types.GenesisState { 28 | var genesisState types.GenesisState 29 | 30 | if appState[ModuleName] != nil { 31 | cdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) 32 | } 33 | 34 | return &genesisState 35 | } 36 | 37 | func ValidateGenesis(data types.GenesisState) error { 38 | err := data.Params.Validate() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // InitGenesis import module genesis 47 | func InitGenesis( 48 | ctx sdk.Context, 49 | k keeper.Keeper, 50 | data types.GenesisState, 51 | ) { 52 | // Validate init contents 53 | if err := ValidateGenesis(data); err != nil { 54 | panic(err) 55 | } 56 | 57 | // Set params 58 | if err := k.SetParams(ctx, data.Params); err != nil { 59 | panic(err) 60 | } 61 | } 62 | 63 | // ExportGenesis export module state 64 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 65 | params := k.GetParams(ctx) 66 | 67 | return &types.GenesisState{ 68 | Params: params, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /x/cadence/genesis_test.go: -------------------------------------------------------------------------------- 1 | package cadence_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | 9 | "github.com/bitsongofficial/go-bitsong/x/cadence" 10 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 11 | ) 12 | 13 | func TestGenesisTestSuite(t *testing.T) { 14 | suite.Run(t, new(CadenceModuleSuite)) 15 | } 16 | 17 | func (suite *CadenceModuleSuite) SetupTest() { 18 | suite.Setup() 19 | } 20 | 21 | func (suite *CadenceModuleSuite) TestClockInitGenesis() { 22 | testCases := []struct { 23 | name string 24 | genesis types.GenesisState 25 | success bool 26 | }{ 27 | { 28 | "Success - Default Genesis", 29 | *cadence.DefaultGenesisState(), 30 | true, 31 | }, 32 | { 33 | "Success - Custom Genesis", 34 | types.GenesisState{ 35 | Params: types.Params{ 36 | ContractGasLimit: 500_000, 37 | }, 38 | }, 39 | true, 40 | }, 41 | { 42 | "Fail - Invalid Gas Amount", 43 | types.GenesisState{ 44 | Params: types.Params{ 45 | ContractGasLimit: 1, 46 | }, 47 | }, 48 | false, 49 | }, 50 | } 51 | 52 | for _, tc := range testCases { 53 | suite.Run(fmt.Sprintf("Case %s", tc.name), func() { 54 | suite.SetupTest() // reset 55 | 56 | if tc.success { 57 | suite.Require().NotPanics(func() { 58 | cadence.InitGenesis(suite.Ctx, suite.App.CadenceKeeper, tc.genesis) 59 | }) 60 | 61 | params := suite.App.CadenceKeeper.GetParams(suite.Ctx) 62 | suite.Require().Equal(tc.genesis.Params, params) 63 | } else { 64 | suite.Require().Panics(func() { 65 | cadence.InitGenesis(suite.Ctx, suite.App.CadenceKeeper, tc.genesis) 66 | }) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /x/cadence/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" 7 | 8 | "cosmossdk.io/log" 9 | 10 | storetypes "cosmossdk.io/store/types" 11 | "github.com/cosmos/cosmos-sdk/codec" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | 14 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 15 | ) 16 | 17 | // Keeper of the cadence store 18 | type Keeper struct { 19 | storeKey storetypes.StoreKey 20 | cdc codec.BinaryCodec 21 | 22 | wasmKeeper wasmkeeper.Keeper 23 | contractKeeper *wasmkeeper.PermissionedKeeper 24 | 25 | authority string 26 | } 27 | 28 | func NewKeeper( 29 | key storetypes.StoreKey, 30 | cdc codec.BinaryCodec, 31 | wasmKeeper wasmkeeper.Keeper, 32 | contractKeeper *wasmkeeper.PermissionedKeeper, 33 | authority string, 34 | ) Keeper { 35 | return Keeper{ 36 | cdc: cdc, 37 | storeKey: key, 38 | wasmKeeper: wasmKeeper, 39 | contractKeeper: contractKeeper, 40 | authority: authority, 41 | } 42 | } 43 | 44 | // Logger returns a module-specific logger. 45 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 46 | return ctx.Logger().With("module", fmt.Sprintf("go-bitsong/%s", types.ModuleName)) 47 | } 48 | 49 | // GetAuthority returns the x/cadence module's authority. 50 | func (k Keeper) GetAuthority() string { 51 | return k.authority 52 | } 53 | 54 | // SetParams sets the x/cadence module parameters. 55 | func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { 56 | if err := p.Validate(); err != nil { 57 | return err 58 | } 59 | 60 | store := ctx.KVStore(k.storeKey) 61 | bz := k.cdc.MustMarshal(&p) 62 | store.Set(types.ParamsKey, bz) 63 | 64 | return nil 65 | } 66 | 67 | // GetParams returns the current x/cadence module parameters. 68 | func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { 69 | store := ctx.KVStore(k.storeKey) 70 | bz := store.Get(types.ParamsKey) 71 | if bz == nil { 72 | return p 73 | } 74 | 75 | k.cdc.MustUnmarshal(bz, &p) 76 | return p 77 | } 78 | 79 | // GetContractKeeper returns the x/wasm module's contract keeper. 80 | func (k Keeper) GetContractKeeper() *wasmkeeper.PermissionedKeeper { 81 | return k.contractKeeper 82 | } 83 | 84 | // GetCdc returns the x/cadence module's codec. 85 | func (k Keeper) GetCdc() codec.BinaryCodec { 86 | return k.cdc 87 | } 88 | 89 | // GetStore returns the x/cadence module's store key. 90 | func (k Keeper) GetStore() storetypes.StoreKey { 91 | return k.storeKey 92 | } 93 | -------------------------------------------------------------------------------- /x/cadence/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 10 | 11 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 12 | ) 13 | 14 | var _ types.MsgServer = &msgServer{} 15 | 16 | // msgServer is a wrapper of Keeper. 17 | type msgServer struct { 18 | Keeper 19 | } 20 | 21 | // NewMsgServerImpl returns an implementation of the x/cadence MsgServer interface. 22 | func NewMsgServerImpl(k Keeper) types.MsgServer { 23 | return &msgServer{ 24 | Keeper: k, 25 | } 26 | } 27 | 28 | // RegisterCadenceContract handles incoming transactions to register cadence contract s. 29 | func (k msgServer) RegisterCadenceContract(goCtx context.Context, req *types.MsgRegisterCadenceContract) (*types.MsgRegisterCadenceContractResponse, error) { 30 | ctx := sdk.UnwrapSDKContext(goCtx) 31 | 32 | // Validate request 33 | if err := req.ValidateBasic(); err != nil { 34 | return nil, err 35 | } 36 | 37 | return &types.MsgRegisterCadenceContractResponse{}, k.RegisterContract(ctx, req.SenderAddress, req.ContractAddress) 38 | } 39 | 40 | // UnregisterCadenceContract handles incoming transactions to unregister cadence contract s. 41 | func (k msgServer) UnregisterCadenceContract(goCtx context.Context, req *types.MsgUnregisterCadenceContract) (*types.MsgUnregisterCadenceContractResponse, error) { 42 | ctx := sdk.UnwrapSDKContext(goCtx) 43 | 44 | // Validate request 45 | if err := req.ValidateBasic(); err != nil { 46 | return nil, err 47 | } 48 | 49 | return &types.MsgUnregisterCadenceContractResponse{}, k.UnregisterContract(ctx, req.SenderAddress, req.ContractAddress) 50 | } 51 | 52 | // UnjailCadenceContract handles incoming transactions to unjail cadence contract s. 53 | func (k msgServer) UnjailCadenceContract(goCtx context.Context, req *types.MsgUnjailCadenceContract) (*types.MsgUnjailCadenceContractResponse, error) { 54 | ctx := sdk.UnwrapSDKContext(goCtx) 55 | 56 | // Validate request 57 | if err := req.ValidateBasic(); err != nil { 58 | return nil, err 59 | } 60 | 61 | return &types.MsgUnjailCadenceContractResponse{}, k.SetJailStatusBySender(ctx, req.SenderAddress, req.ContractAddress, false) 62 | } 63 | 64 | func (k msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { 65 | if k.authority != req.Authority { 66 | return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, req.Authority) 67 | } 68 | 69 | ctx := sdk.UnwrapSDKContext(goCtx) 70 | if err := k.SetParams(ctx, req.Params); err != nil { 71 | return nil, err 72 | } 73 | 74 | return &types.MsgUpdateParamsResponse{}, nil 75 | } 76 | -------------------------------------------------------------------------------- /x/cadence/keeper/querier.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | 9 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 10 | ) 11 | 12 | var _ types.QueryServer = &Querier{} 13 | 14 | type Querier struct { 15 | keeper Keeper 16 | } 17 | 18 | func NewQuerier(k Keeper) Querier { 19 | return Querier{ 20 | keeper: k, 21 | } 22 | } 23 | 24 | // ContractModules returns contract addresses which are using the cadence 25 | func (q Querier) CadenceContracts(stdCtx context.Context, req *types.QueryCadenceContracts) (*types.QueryCadenceContractsResponse, error) { 26 | ctx := sdk.UnwrapSDKContext(stdCtx) 27 | 28 | contracts, err := q.keeper.GetPaginatedContracts(ctx, req.Pagination) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return contracts, nil 34 | } 35 | 36 | // CadenceContract returns the cadence contract information 37 | func (q Querier) CadenceContract(stdCtx context.Context, req *types.QueryCadenceContract) (*types.QueryCadenceContractResponse, error) { 38 | ctx := sdk.UnwrapSDKContext(stdCtx) 39 | 40 | // Ensure the contract address is valid 41 | if _, err := sdk.AccAddressFromBech32(req.ContractAddress); err != nil { 42 | return nil, sdkerrors.ErrInvalidAddress 43 | } 44 | 45 | contract, err := q.keeper.GetCadenceContract(ctx, req.ContractAddress) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return &types.QueryCadenceContractResponse{ 51 | CadenceContract: *contract, 52 | }, nil 53 | } 54 | 55 | // Params returns the total set of cadence parameters. 56 | func (q Querier) Params(stdCtx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { 57 | ctx := sdk.UnwrapSDKContext(stdCtx) 58 | 59 | p := q.keeper.GetParams(ctx) 60 | 61 | return &types.QueryParamsResponse{ 62 | Params: &p, 63 | }, nil 64 | } 65 | -------------------------------------------------------------------------------- /x/cadence/keeper/testdata/clock_example.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/cadence/keeper/testdata/clock_example.wasm -------------------------------------------------------------------------------- /x/cadence/keeper/testdata/cw_testburn.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/cadence/keeper/testdata/cw_testburn.wasm -------------------------------------------------------------------------------- /x/cadence/module_test.go: -------------------------------------------------------------------------------- 1 | package cadence_test 2 | 3 | import apptesting "github.com/bitsongofficial/go-bitsong/app/testing" 4 | 5 | type CadenceModuleSuite struct { 6 | apptesting.KeeperTestHelper 7 | } 8 | -------------------------------------------------------------------------------- /x/cadence/spec/01_concepts.md: -------------------------------------------------------------------------------- 1 | 2 | # Concepts 3 | 4 | ## Clock 5 | 6 | The Clock module allows registered contracts to be executed at the end of every block. This allows the smart contract to perform regular and routine actions without the need for external bots. Developers can setup their contract with x/Clock by registering their contract with the module. Once registered, the contract will be executed at the end of every block. If the contract throws an error during execution or exceeds the gas limit defined in the module's parameters, the contract will be jailed and no longer executed. The contract can be unjailed by the contract admin. 7 | 8 | ## Registering a Contract 9 | 10 | Register a contract with x/Clock by executing the following transaction: 11 | 12 | ```bash 13 | bsd tx cadence register [contract_address] 14 | ``` 15 | 16 | > Note: the sender of this transaction must be the contract admin, if exists, or else the contract creator. 17 | 18 | The `contract_address` is the bech32 address of the contract to be executed at the end of every block. Once registered, the contract will be executed at the end of every block. Please ensure that your contract follows the guidelines outlined in [Integration](03_integration.md). 19 | 20 | ## Unjailing a Contract 21 | 22 | A contract can be unjailed by executing the following transaction: 23 | 24 | ```bash 25 | btsgd tx cadence unjail [contract_address] 26 | ``` 27 | 28 | > Note: the sender of this transaction must be the contract admin, if exists, or else the contract creator. 29 | 30 | The `contract_address` is the bech32 address of the contract to be unjailed. Unjailing a contract will allow it to be executed at the end of every block. If your contract becomes jailed, please see [Integration](03_integration.md) to ensure the contract is setup with a Sudo message. 31 | 32 | ## Unregistering a Contract 33 | 34 | A contract can be unregistered by executing the following transaction: 35 | 36 | ```bash 37 | btsgd tx cadence unregister [contract_address] 38 | ``` 39 | 40 | > Note: the sender of this transaction must be the contract admin, if exists, or else the contract creator. 41 | 42 | The `contract_address` is the bech32 address of the contract to be unregistered. Unregistering a contract will remove it from the Clock module. This means that the contract will no longer be executed at the end of every block. -------------------------------------------------------------------------------- /x/cadence/spec/02_state.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | ## State Objects 4 | 5 | The `x/cadence` module only manages the following object in state: CadenceContract. This object is used to store the address of the contract and its jail status. The jail status is used to determine if the contract should be executed at the end of every block. If the contract is jailed, it will not be executed. 6 | 7 | ```go 8 | // This object is used to store the contract address and the 9 | // jail status of the contract. 10 | message CadenceContract { 11 | // The address of the contract. 12 | string contract_address = 1; 13 | // The jail status of the contract. 14 | bool is_jailed = 2; 15 | } 16 | ``` 17 | 18 | ## Genesis & Params 19 | 20 | The `x/cadence` module's `GenesisState` defines the state necessary for initializing the chain from a previously exported height. It simply contains the gas limit parameter which is used to determine the maximum amount of gas that can be used by a contract. This value can be modified with a governance proposal. 21 | 22 | ```go 23 | // GenesisState - initial state of module 24 | message GenesisState { 25 | // Params of this module 26 | Params params = 1 [ 27 | (gogoproto.nullable) = false, 28 | (gogoproto.jsontag) = "params,omitempty" 29 | ]; 30 | } 31 | 32 | // Params defines the set of module parameters. 33 | message Params { 34 | // contract_gas_limit defines the maximum amount of gas that can be used by a contract. 35 | uint64 contract_gas_limit = 1 [ 36 | (gogoproto.jsontag) = "contract_gas_limit,omitempty", 37 | (gogoproto.moretags) = "yaml:\"contract_gas_limit\"" 38 | ]; 39 | } 40 | ``` 41 | 42 | ## State Transitions 43 | 44 | The following state transitions are possible: 45 | 46 | - Register a contract creates a new CadenceContract object in state. 47 | - Jailing a contract updates the is_jailed field of a CadenceContract object in state. 48 | - Unjailing a contract updates the is_jailed field of a CadenceContract object in state. 49 | - Unregister a contract deletes a CadenceContract object from state. -------------------------------------------------------------------------------- /x/cadence/spec/README.md: -------------------------------------------------------------------------------- 1 | # `cadence` 2 | 3 | ## Abstract 4 | 5 | This document specifies the internal `x/cadence` module of Bitsong Network. This is a fork of the `x/clock` module from Juno. 6 | 7 | The `x/cadence` module allows specific contracts to be executed at the end of every block. This allows the smart contract to perform actions that may need to happen every block or at set block intervals. 8 | 9 | By using this module, your application can remove the headache of external whitelisted bots and instead depend on the chain itself for constant executions. 10 | 11 | ## Contents 12 | 13 | 1. **[Concepts](01_concepts.md)** 14 | 2. **[State](02_state.md)** 15 | 3. **[Contract Integration](03_integration.md)** 16 | -------------------------------------------------------------------------------- /x/cadence/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | ) 10 | 11 | var ( 12 | amino = codec.NewLegacyAmino() 13 | AminoCdc = codec.NewAminoCodec(amino) 14 | ) 15 | 16 | func init() { 17 | RegisterLegacyAminoCodec(amino) 18 | cryptocodec.RegisterCrypto(amino) 19 | sdk.RegisterLegacyAminoCodec(amino) 20 | 21 | } 22 | 23 | // RegisterLegacyAminoCodec registers concrete types on the LegacyAmino codec 24 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 25 | cdc.RegisterConcrete(&MsgRegisterCadenceContract{}, "cadence/MsgRegisterCadenceContract", nil) 26 | cdc.RegisterConcrete(&MsgUnregisterCadenceContract{}, "cadence/MsgUnregisterCadenceContract", nil) 27 | cdc.RegisterConcrete(&MsgUnjailCadenceContract{}, "cadence/MsgUnjailCadenceContract", nil) 28 | cdc.RegisterConcrete(&MsgUpdateParams{}, "cadence/MsgUpdateParams", nil) 29 | } 30 | 31 | func RegisterInterfaces(registry types.InterfaceRegistry) { 32 | registry.RegisterImplementations( 33 | (*sdk.Msg)(nil), 34 | &MsgRegisterCadenceContract{}, 35 | &MsgUnregisterCadenceContract{}, 36 | &MsgUnjailCadenceContract{}, 37 | &MsgUpdateParams{}, 38 | ) 39 | 40 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 41 | } 42 | -------------------------------------------------------------------------------- /x/cadence/types/codec_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | 8 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | ) 11 | 12 | type CodecTestSuite struct { 13 | suite.Suite 14 | } 15 | 16 | func TestCodecSuite(t *testing.T) { 17 | suite.Run(t, new(CodecTestSuite)) 18 | } 19 | 20 | func (suite *CodecTestSuite) TestRegisterInterfaces() { 21 | registry := codectypes.NewInterfaceRegistry() 22 | registry.RegisterInterface(sdk.MsgInterfaceProtoName, (*sdk.Msg)(nil)) 23 | RegisterInterfaces(registry) 24 | 25 | impls := registry.ListImplementations(sdk.MsgInterfaceProtoName) 26 | suite.Require().Equal(4, len(impls)) 27 | suite.Require().ElementsMatch([]string{ 28 | "/bitsong.cadence.v1.MsgUpdateParams", 29 | "/bitsong.cadence.v1.MsgRegisterCadenceContract", 30 | "/bitsong.cadence.v1.MsgUnregisterCadenceContract", 31 | "/bitsong.cadence.v1.MsgUnjailCadenceContract", 32 | }, impls) 33 | } 34 | -------------------------------------------------------------------------------- /x/cadence/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | ) 6 | 7 | var ( 8 | ErrContractJailed = errorsmod.Register(ModuleName, 1, "contract is jailed") 9 | ErrContractNotJailed = errorsmod.Register(ModuleName, 2, "contract is not jailed") 10 | ErrContractAlreadyJailed = errorsmod.Register(ModuleName, 3, "contract is already jailed") 11 | ) 12 | -------------------------------------------------------------------------------- /x/cadence/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | ) 7 | 8 | var ParamsKey = []byte{0x00} 9 | 10 | const ( 11 | ModuleName = "cade" 12 | 13 | RouterKey = ModuleName 14 | 15 | StoreKey = ModuleName 16 | 17 | QuerierRoute = ModuleName 18 | ) 19 | 20 | const codespace = "bitsong-global" 21 | 22 | var ( 23 | ErrInvalidAddress = sdkerrors.ErrInvalidAddress 24 | ErrContractNotRegistered = errorsmod.Register(codespace, 1, "contract not registered") 25 | ErrContractAlreadyRegistered = errorsmod.Register(codespace, 2, "contract already registered") 26 | ErrContractNotAdmin = errorsmod.Register(codespace, 3, "sender is not the contract admin") 27 | ErrContractNotCreator = errorsmod.Register(codespace, 4, "sender is not the contract creator") 28 | ErrInvalidCWContract = errorsmod.Register(codespace, 5, "invalid CosmWasm contract") 29 | ErrOutOfGas = errorsmod.Register(codespace, 6, "contract execution ran out of gas") 30 | ErrContractExecutionPanic = errorsmod.Register(codespace, 7, "contract execution panicked") 31 | ) 32 | -------------------------------------------------------------------------------- /x/cadence/types/msgs_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | type MsgsTestSuite struct { 12 | suite.Suite 13 | govModule string 14 | } 15 | 16 | func TestMsgsTestSuite(t *testing.T) { 17 | suite.Run(t, new(MsgsTestSuite)) 18 | } 19 | 20 | func (suite *MsgsTestSuite) SetupTest() { 21 | suite.govModule = "juno10d07y265gmmuvt4z0w9aw880jnsr700jvss730" 22 | } 23 | 24 | func (suite *MsgsTestSuite) TestMsgUpdateParams() { 25 | var limit uint64 = 100_000 26 | 27 | p := MsgUpdateParams{ 28 | Authority: suite.govModule, 29 | Params: Params{ 30 | ContractGasLimit: limit, 31 | }, 32 | } 33 | 34 | acc, _ := sdk.AccAddressFromBech32(p.Authority) 35 | 36 | msg := NewMsgUpdateParams(acc, limit) 37 | 38 | suite.Require().Equal(RouterKey, msg.Route()) 39 | suite.Require().Equal(TypeMsgUpdateParams, msg.Type()) 40 | suite.Require().NotNil(msg.GetSigners()) 41 | } 42 | -------------------------------------------------------------------------------- /x/cadence/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | ) 8 | 9 | // DefaultParams returns default parameters 10 | func DefaultParams() Params { 11 | return Params{ 12 | ContractGasLimit: 100_000, 13 | } 14 | } 15 | 16 | // NewParams creates a new Params object 17 | func NewParams( 18 | contractGasLimit uint64, 19 | ) Params { 20 | return Params{ 21 | ContractGasLimit: contractGasLimit, 22 | } 23 | } 24 | 25 | // Validate performs basic validation. 26 | func (p Params) Validate() error { 27 | minimumGas := uint64(100_000) 28 | if p.ContractGasLimit < minimumGas { 29 | return errorsmod.Wrapf( 30 | sdkerrors.ErrInvalidRequest, 31 | "invalid contract gas limit: %d. Must be above %d", p.ContractGasLimit, minimumGas, 32 | ) 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /x/cadence/types/params_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/bitsongofficial/go-bitsong/x/cadence/types" 9 | ) 10 | 11 | func TestParamsValidate(t *testing.T) { 12 | testCases := []struct { 13 | name string 14 | params types.Params 15 | success bool 16 | }{ 17 | { 18 | "Success - Default", 19 | types.DefaultParams(), 20 | true, 21 | }, 22 | { 23 | "Success - Meets min Gas", 24 | types.NewParams(100_000), 25 | true, 26 | }, 27 | { 28 | "Success - Meets min Gas", 29 | types.NewParams(500_000), 30 | true, 31 | }, 32 | { 33 | "Fail - Not Enough Gas", 34 | types.NewParams(1), 35 | false, 36 | }, 37 | { 38 | "Fail - Not Enough Gas", 39 | types.NewParams(100), 40 | false, 41 | }, 42 | { 43 | "Fail - Not Enough Gas", 44 | types.NewParams(1_000), 45 | false, 46 | }, 47 | { 48 | "Fail - Not Enough Gas", 49 | types.NewParams(10_000), 50 | false, 51 | }, 52 | } 53 | 54 | for _, tc := range testCases { 55 | err := tc.params.Validate() 56 | 57 | if tc.success { 58 | require.NoError(t, err, tc.name) 59 | } else { 60 | require.Error(t, err, tc.name) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /x/fantoken/client/cli/flags.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | flag "github.com/spf13/pflag" 5 | ) 6 | 7 | const ( 8 | FlagSymbol = "symbol" 9 | FlagName = "name" 10 | FlagMaxSupply = "max-supply" 11 | FlagRecipient = "recipient" 12 | FlagNewAuthority = "new-authority" 13 | FlagNewMinter = "new-minter" 14 | FlagAmount = "amount" 15 | FlagURI = "uri" 16 | ) 17 | 18 | var ( 19 | FsIssue = flag.NewFlagSet("", flag.ContinueOnError) 20 | FsMint = flag.NewFlagSet("", flag.ContinueOnError) 21 | FsDisableMint = flag.NewFlagSet("", flag.ContinueOnError) 22 | FsSetAuthority = flag.NewFlagSet("", flag.ContinueOnError) 23 | FsSetMinter = flag.NewFlagSet("", flag.ContinueOnError) 24 | FsSetUri = flag.NewFlagSet("", flag.ContinueOnError) 25 | ) 26 | 27 | func init() { 28 | FsIssue.String(FlagSymbol, "", "The fantoken symbol. Once created, it cannot be modified") 29 | FsIssue.String(FlagName, "", "The fantoken name, e.g. Bitsong Network") 30 | FsIssue.String(FlagMaxSupply, "", "The maximum supply of the fantoken") 31 | FsIssue.String(FlagURI, "", "The fantoken uri") 32 | 33 | FsMint.String(FlagRecipient, "", "Address to which the fantoken is to be minted") 34 | 35 | FsDisableMint.String(FlagName, "[do-not-modify]", "The fantoken name, e.g. IRIS Network") 36 | FsDisableMint.String(FlagMaxSupply, "", "The maximum supply of the fantoken") 37 | 38 | FsSetAuthority.String(FlagNewAuthority, "", "The new authority") 39 | 40 | FsSetMinter.String(FlagNewMinter, "", "The new minter") 41 | 42 | FsSetUri.String(FlagURI, "", "The uri of the fantoken") 43 | } 44 | -------------------------------------------------------------------------------- /x/fantoken/client/proposal_handler.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/bitsongofficial/go-bitsong/x/fantoken/client/cli" 5 | govclient "github.com/cosmos/cosmos-sdk/x/gov/client" 6 | ) 7 | 8 | var ProposalHandler = govclient.NewProposalHandler(cli.GetCmdUpdateFantokenFees) 9 | -------------------------------------------------------------------------------- /x/fantoken/genesis.go: -------------------------------------------------------------------------------- 1 | package fantoken 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/bitsongofficial/go-bitsong/x/fantoken/keeper" 7 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 8 | ) 9 | 10 | // InitGenesis stores the genesis state 11 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, data types.GenesisState) { 12 | if err := data.Validate(); err != nil { 13 | panic(err.Error()) 14 | } 15 | 16 | k.SetParamSet(ctx, data.Params) 17 | 18 | // init fan tokens 19 | for _, fantoken := range data.FanTokens { 20 | if err := k.AddFanToken(ctx, &fantoken); err != nil { 21 | panic(err.Error()) 22 | } 23 | } 24 | } 25 | 26 | // ExportGenesis outputs the genesis state 27 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 28 | return &types.GenesisState{ 29 | Params: k.GetParamSet(ctx), 30 | FanTokens: k.GetFanTokens(ctx, nil), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /x/fantoken/handler.go: -------------------------------------------------------------------------------- 1 | package fantoken 2 | 3 | import ( 4 | "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | 8 | govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 9 | 10 | "github.com/bitsongofficial/go-bitsong/x/fantoken/keeper" 11 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 12 | ) 13 | 14 | // // NewHandler handles all fantoken type messages. 15 | // func NewHandler(k keeper.Keeper) sdk.Handler { 16 | // msgServer := keeper.NewMsgServerImpl(&k) 17 | 18 | // return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { 19 | // ctx = ctx.WithEventManager(sdk.NewEventManager()) 20 | 21 | // switch msg := msg.(type) { 22 | // case *types.MsgIssue: 23 | // res, err := msgServer.Issue(sdk.WrapSDKContext(ctx), msg) 24 | // return sdk.WrapServiceResult(ctx, res, err) 25 | 26 | // case *types.MsgDisableMint: 27 | // res, err := msgServer.DisableMint(sdk.WrapSDKContext(ctx), msg) 28 | // return sdk.WrapServiceResult(ctx, res, err) 29 | 30 | // case *types.MsgMint: 31 | // res, err := msgServer.Mint(sdk.WrapSDKContext(ctx), msg) 32 | // return sdk.WrapServiceResult(ctx, res, err) 33 | 34 | // case *types.MsgBurn: 35 | // res, err := msgServer.Burn(sdk.WrapSDKContext(ctx), msg) 36 | // return sdk.WrapServiceResult(ctx, res, err) 37 | 38 | // case *types.MsgSetAuthority: 39 | // res, err := msgServer.SetAuthority(sdk.WrapSDKContext(ctx), msg) 40 | // return sdk.WrapServiceResult(ctx, res, err) 41 | 42 | // case *types.MsgSetMinter: 43 | // res, err := msgServer.SetMinter(sdk.WrapSDKContext(ctx), msg) 44 | // return sdk.WrapServiceResult(ctx, res, err) 45 | 46 | // case *types.MsgSetUri: 47 | // res, err := msgServer.SetUri(sdk.WrapSDKContext(ctx), msg) 48 | // return sdk.WrapServiceResult(ctx, res, err) 49 | 50 | // default: 51 | // return nil, errors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) 52 | // } 53 | // } 54 | // } 55 | 56 | func NewProposalHandler(k keeper.Keeper) govv1beta1.Handler { 57 | return func(ctx sdk.Context, content govv1beta1.Content) error { 58 | switch c := content.(type) { 59 | case *types.UpdateFeesProposal: 60 | return handleUpdateFeesProposal(ctx, k, c) 61 | 62 | default: 63 | return errors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized fantoken proposal content type: %T", c) 64 | } 65 | } 66 | } 67 | 68 | func handleUpdateFeesProposal(ctx sdk.Context, k keeper.Keeper, p *types.UpdateFeesProposal) error { 69 | ctx.Logger().Info("Updating fantoken fees from proposal") 70 | 71 | if err := types.ValidateFees(p.IssueFee, p.MintFee, p.BurnFee); err != nil { 72 | return err 73 | } 74 | 75 | params := k.GetParamSet(ctx) 76 | params.IssueFee = p.IssueFee 77 | params.MintFee = p.MintFee 78 | params.BurnFee = p.BurnFee 79 | k.SetParamSet(ctx, params) 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /x/fantoken/keeper/fantoken.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "cosmossdk.io/math" 5 | storetypes "cosmossdk.io/store/types" 6 | db "github.com/cosmos/cosmos-db" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | gogotypes "github.com/cosmos/gogoproto/types" 10 | 11 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 12 | ) 13 | 14 | // GetFanTokens returns all existing fantokens 15 | func (k Keeper) GetFanTokens(ctx sdk.Context, owner sdk.AccAddress) (fantokens []types.FanToken) { 16 | store := ctx.KVStore(k.storeKey) 17 | 18 | var it db.Iterator 19 | if owner == nil { 20 | it = storetypes.KVStorePrefixIterator(store, types.PrefixFanTokenForDenom) 21 | defer it.Close() 22 | 23 | for ; it.Valid(); it.Next() { 24 | var fantoken types.FanToken 25 | k.cdc.MustUnmarshal(it.Value(), &fantoken) 26 | 27 | fantokens = append(fantokens, fantoken) 28 | } 29 | return 30 | } 31 | 32 | it = storetypes.KVStorePrefixIterator(store, types.KeyFanTokens(owner, "")) 33 | defer it.Close() 34 | 35 | for ; it.Valid(); it.Next() { 36 | var denom gogotypes.StringValue 37 | k.cdc.MustUnmarshal(it.Value(), &denom) 38 | 39 | fantoken, err := k.getFanTokenByDenom(ctx, denom.Value) 40 | if err != nil { 41 | continue 42 | } 43 | fantokens = append(fantokens, fantoken) 44 | } 45 | return 46 | } 47 | 48 | // GetFanToken returns the fantoken of the specified denom 49 | func (k Keeper) GetFanToken(ctx sdk.Context, denom string) (*types.FanToken, error) { 50 | // query fantoken by denom 51 | if fantoken, err := k.getFanTokenByDenom(ctx, denom); err == nil { 52 | return &fantoken, nil 53 | } 54 | 55 | return nil, sdkerrors.ErrAppConfig.Wrapf(types.ErrFanTokenNotExists.Error(), "denom %s does not exist", denom) 56 | } 57 | 58 | // AddFanToken saves a new token 59 | func (k Keeper) AddFanToken(ctx sdk.Context, token *types.FanToken) error { 60 | if k.HasFanToken(ctx, token.GetDenom()) { 61 | return sdkerrors.ErrAppConfig.Wrapf(types.ErrDenomAlreadyExists.Error(), "denom already exists: %s", token.GetDenom()) 62 | } 63 | 64 | // set token 65 | k.setFanToken(ctx, token) 66 | 67 | if len(token.MetaData.Authority) != 0 { 68 | // set token to be prefixed with metadata authority 69 | k.setWithMetadataAuthority(ctx, token.GetAuthority(), token.GetDenom()) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | // getFanTokenSupply queries the fantoken supply from the total supply 76 | func (k Keeper) getFanTokenSupply(ctx sdk.Context, denom string) math.Int { 77 | return k.bankKeeper.GetSupply(ctx, denom).Amount 78 | } 79 | -------------------------------------------------------------------------------- /x/fantoken/keeper/fees.go: -------------------------------------------------------------------------------- 1 | // nolint 2 | package keeper 3 | 4 | import ( 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | // deductIssueFee performs fee handling for issuing token 9 | func (k Keeper) deductIssueFee(ctx sdk.Context, authority sdk.AccAddress) error { 10 | params := k.GetParamSet(ctx) 11 | 12 | // check if amount is zero 13 | if params.IssueFee.Amount.IsZero() || params.IssueFee.Amount.IsNegative() { 14 | return nil 15 | } 16 | 17 | // send issue fantoken fee to community pool 18 | return k.poolKeeper.FundCommunityPool(ctx, sdk.Coins{params.IssueFee}, authority) 19 | } 20 | 21 | // deductMintFee performs fee handling for minting token 22 | func (k Keeper) deductMintFee(ctx sdk.Context, authority sdk.AccAddress) error { 23 | params := k.GetParamSet(ctx) 24 | 25 | // check if amount is zero 26 | if params.MintFee.Amount.IsZero() || params.MintFee.Amount.IsNegative() { 27 | return nil 28 | } 29 | 30 | // send Mint fantoken fee to community pool 31 | return k.poolKeeper.FundCommunityPool(ctx, sdk.Coins{params.MintFee}, authority) 32 | } 33 | 34 | // deductBurnFee performs fee handling for burning token 35 | func (k Keeper) deductBurnFee(ctx sdk.Context, authority sdk.AccAddress) error { 36 | params := k.GetParamSet(ctx) 37 | 38 | // check if amount is zero 39 | if params.BurnFee.Amount.IsZero() || params.BurnFee.Amount.IsNegative() { 40 | return nil 41 | } 42 | 43 | // send Burn fantoken fee to community pool 44 | return k.poolKeeper.FundCommunityPool(ctx, sdk.Coins{params.BurnFee}, authority) 45 | } 46 | -------------------------------------------------------------------------------- /x/fantoken/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | // GetParamSet returns fantoken params from the global param store 9 | func (k Keeper) GetParamSet(ctx sdk.Context) types.Params { 10 | var p types.Params 11 | k.paramSpace.GetParamSet(ctx, &p) 12 | return p 13 | } 14 | 15 | // SetParamSet sets fantoken params to the global param store 16 | func (k Keeper) SetParamSet(ctx sdk.Context, params types.Params) { 17 | k.paramSpace.SetParamSet(ctx, ¶ms) 18 | } 19 | -------------------------------------------------------------------------------- /x/fantoken/keeper/store.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "cosmossdk.io/errors" 7 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | gogotypes "github.com/gogo/protobuf/types" 10 | ) 11 | 12 | // HasFanToken asserts a fantoken exists 13 | func (k Keeper) HasFanToken(ctx sdk.Context, denom string) bool { 14 | store := ctx.KVStore(k.storeKey) 15 | return store.Has(types.KeyDenom(denom)) 16 | } 17 | 18 | func (k Keeper) setWithMetadataAuthority(ctx sdk.Context, owner sdk.AccAddress, denom string) { 19 | store := ctx.KVStore(k.storeKey) 20 | bz := k.cdc.MustMarshal(&gogotypes.StringValue{Value: denom}) 21 | store.Set(types.KeyFanTokens(owner, denom), bz) 22 | } 23 | 24 | func (k Keeper) setFanToken(ctx sdk.Context, token *types.FanToken) { 25 | store := ctx.KVStore(k.storeKey) 26 | bz := k.cdc.MustMarshal(token) 27 | store.Set(types.KeyDenom(token.GetDenom()), bz) 28 | } 29 | 30 | func (k Keeper) getFanTokenByDenom(ctx sdk.Context, denom string) (fantoken types.FanToken, err error) { 31 | store := ctx.KVStore(k.storeKey) 32 | 33 | bz := store.Get(types.KeyDenom(denom)) 34 | if bz == nil { 35 | return fantoken, errors.Wrap(types.ErrFanTokenNotExists, fmt.Sprintf("fantoken denom %s does not exist", denom)) 36 | } 37 | 38 | k.cdc.MustUnmarshal(bz, &fantoken) 39 | return fantoken, nil 40 | } 41 | 42 | // reset all indices by the new owner for fantoken query 43 | func (k Keeper) resetStoreKeyForQueryToken(ctx sdk.Context, denom string, srcOwner, dstOwner sdk.AccAddress) { 44 | store := ctx.KVStore(k.storeKey) 45 | 46 | // delete the old key 47 | store.Delete(types.KeyFanTokens(srcOwner, denom)) 48 | 49 | // add the new key 50 | k.setWithMetadataAuthority(ctx, dstOwner, denom) 51 | } 52 | -------------------------------------------------------------------------------- /x/fantoken/simulation/genesis.go: -------------------------------------------------------------------------------- 1 | package simulation 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "cosmossdk.io/math" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/cosmos/cosmos-sdk/types/module" 10 | "github.com/cosmos/cosmos-sdk/types/simulation" 11 | simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 12 | 13 | "github.com/bitsongofficial/go-bitsong/x/fantoken/types" 14 | ) 15 | 16 | // RandomizedGenState generates a random GenesisState for distribution 17 | func RandomizedGenState(simState *module.SimulationState) { 18 | // 1. Randomize module parameters 19 | bondDenom := simState.BondDenom 20 | 21 | issueFee := sdk.Coin{ 22 | Denom: bondDenom, 23 | Amount: math.NewInt(int64(simulation.RandIntBetween(simState.Rand, 100, 1000000))), 24 | } 25 | mintFee := sdk.Coin{ 26 | Denom: bondDenom, 27 | Amount: math.NewInt(int64(simulation.RandIntBetween(simState.Rand, 10, 100000))), 28 | } 29 | burnFee := sdk.Coin{ 30 | Denom: bondDenom, 31 | Amount: math.NewInt(int64(simulation.RandIntBetween(simState.Rand, 10, 100000))), 32 | } 33 | 34 | params := types.Params{ 35 | IssueFee: issueFee, 36 | MintFee: mintFee, 37 | BurnFee: burnFee, 38 | } 39 | 40 | // 2. Generate random fantokens 41 | numFanTokens := simState.Rand.Intn(5) + 1 // 1 to 5 tokens 42 | fanTokens := make([]types.FanToken, numFanTokens) 43 | 44 | for i := 0; i < numFanTokens; i++ { 45 | denom := "ft" + simtypes.RandStringOfLength(simState.Rand, 6) 46 | maxSupply := int64(simulation.RandIntBetween(simState.Rand, 1000, 10000000)) 47 | 48 | // Select a random account for minter 49 | minterAcc := simState.Accounts[simState.Rand.Intn(len(simState.Accounts))] 50 | minter := minterAcc.Address.String() 51 | 52 | // Random metadata 53 | name := fmt.Sprintf("Random FanToken %d", i) 54 | symbol := strings.ToUpper(simtypes.RandStringOfLength(simState.Rand, 4)) 55 | 56 | var uri string 57 | if simState.Rand.Intn(2) == 0 { 58 | uri = fmt.Sprintf("https://example.com/token/%s", simtypes.RandStringOfLength(simState.Rand, 8)) 59 | } 60 | 61 | authorityAcc := simState.Accounts[simState.Rand.Intn(len(simState.Accounts))] 62 | authority := authorityAcc.Address.String() 63 | 64 | metaData := types.Metadata{ 65 | Name: name, 66 | Symbol: symbol, 67 | URI: uri, 68 | Authority: authority, 69 | } 70 | 71 | fanTokens[i] = types.FanToken{ 72 | Denom: denom, 73 | MaxSupply: math.NewInt(maxSupply), 74 | Minter: minter, 75 | MetaData: metaData, 76 | } 77 | } 78 | 79 | // 3. Construct and marshal the randomized genesis state 80 | genesisState := &types.GenesisState{ 81 | Params: params, 82 | FanTokens: fanTokens, 83 | } 84 | 85 | simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) 86 | } 87 | -------------------------------------------------------------------------------- /x/fantoken/spec/02_state.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # State 6 | 7 | The `fantoken` module keeps track of [**parameters**](#Params) and [**fan tokens**](#Token). 8 | 9 | ``` 10 | Params: types.Params 11 | FanTokens: []types.FanToken 12 | ``` 13 | 14 | ## Params 15 | 16 | In the state definition, we can find the **Params**. This section corresponds to a module-wide configuration structure that stores system parameters. In particular, it defines the overall fantoken module functioning and contains the **issueFee**, **mintFee** and **burnFee** for the _fan token_. Such an implementation allows governance to decide the issue fee, but also the mint and burn fees the users have to pay to perform these operations with the tokens, in an arbitrary way - since proposals can modify it. 17 | 18 | ```go 19 | type Params struct { 20 | IssueFee sdk.Coin 21 | MintFee sdk.Coin 22 | BurnFee sdk.Coin 23 | } 24 | ``` 25 | 26 | ## Fantoken 27 | 28 | The state contains a list of **Fantokens**. They are [fan tokens](01_concepts.md#Fan-token) (fungible tokens deriving by the ERC-20 Standard), and their state information is: 29 | 30 | - **Denom**, that corresponds to the identifier of the fan token. It is a `string`, automatically calculated on the first `Minter`, `Symbol`, `Name` and `Block Height` of the issuing transaction of the _fan token_ as explained in [concepts](01_concepts.md#Fan-token), and _cannot change_ for the whole life of the token; 31 | - **MaxSupply**, that represents the upper limit for the total supply of the tokens. More specifically, it is an `integer number`, expressed in micro unit (![formula](https://render.githubusercontent.com/render/math?math=\color{gray}\mu=10^{-6})) as explained in [concepts](01_concepts.md#Fan-token), that _cannot change_ for the whole life of the token and which corresponds to the maximum number the supply can reach in any moment; 32 | - **Minter**, which corresponds to the address of the current `minter` for the token. It is an address and _can change_ during the token lifecycle thanks to the **minting ability transfer**. When the `minter` address is set to an empty value, the token can be minted no more; 33 | - **MetaData**, which contains metadata for the _fan token_ and is made up of the `Name`, the `Symbol`, a `URI` and an `Authority` as described in [concepts](01_concepts.md#Fan-token). 34 | 35 | More specifically, the `metadata` _can change_ during the life of the token according to: 36 | - **URI** can be changed by the `authority`. It can be changed until when the authority is available; 37 | - **Authority** which can be transferred by the current authority until when the `authority` itself is not set to an empty value. 38 | 39 | ```go 40 | type FanToken struct { 41 | Denom string 42 | MaxSupply sdk.Int 43 | Minter string 44 | MetaData types.Metadata 45 | } 46 | 47 | type Metadata struct { 48 | Name string 49 | Symbol string 50 | URI string 51 | Authority string 52 | } 53 | ``` -------------------------------------------------------------------------------- /x/fantoken/spec/04_events.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Events 6 | 7 | The fantoken module emits the following events: 8 | ## EventIssue 9 | 10 | | Type | Attribute Key | Attribute Value | 11 | | :-------------- | :------------ | :--------------- | 12 | | message | action | `/bitsong.fantoken.v1beta1.MsgIssue` | 13 | | bitsong.fantoken.v1beta1.EventIssue | denom | {denom} | 14 | 15 | ## EventDisableMint 16 | 17 | | Type | Attribute Key | Attribute Value | 18 | | :-------------- | :------------ | :--------------- | 19 | | message | action | `/bitsong.fantoken.v1beta1.MsgDisableMint` | 20 | | bitsong.fantoken.v1beta1.EventDisableMint | denom | {denom} | 21 | 22 | ## EventMint 23 | 24 | | Type | Attribute Key | Attribute Value | 25 | | :----------------------- | :------------ | :---------------- | 26 | | message | action | `/bitsong.fantoken.v1beta1.MsgMint` | 27 | | bitsong.fantoken.v1beta1.EventMint | recipient | {recipient} | 28 | | bitsong.fantoken.v1beta1.EventMint | coin | {coin} | 29 | 30 | ## EventBurn 31 | 32 | | Type | Attribute Key | Attribute Value | 33 | | :------------- | :------------ | :----------------- | 34 | | message | action | `/bitsong.fantoken.v1beta1.MsgBurn` | 35 | | bitsong.fantoken.v1beta1.EventBurn | sender | {sender} | 36 | | bitsong.fantoken.v1beta1.EventBurn | coin | {coin} | 37 | 38 | ## EventSetAuthority 39 | 40 | | Type | Attribute Key | Attribute Value | 41 | | :------------- | :------------ | :-------------- | 42 | | message | action | `/bitsong.fantoken.v1beta1.MsgSetAuthority` | 43 | | bitsong.fantoken.v1beta1.EventTransferAuthority | denom | {denom} | 44 | | bitsong.fantoken.v1beta1.EventTransferAuthority | old_authority | {old_authority} | 45 | | bitsong.fantoken.v1beta1.EventTransferAuthority | new_authority | {new_authority} | 46 | 47 | ## EventSetMinter 48 | 49 | | Type | Attribute Key | Attribute Value | 50 | | :------------- | :------------ | :-------------- | 51 | | message | action | `/bitsong.fantoken.v1beta1.MsgSetMinter` | 52 | | bitsong.fantoken.v1beta1.EventTransferMinter | denom | {denom} | 53 | | bitsong.fantoken.v1beta1.EventTransferMinter | old_minter | {old_minter} | 54 | | bitsong.fantoken.v1beta1.EventTransferMinter | new_authority | {new_minter} | 55 | 56 | ## EventSetUri 57 | 58 | | Type | Attribute Key | Attribute Value | 59 | | :-------------- | :------------ | :--------------- | 60 | | message | action | `/bitsong.fantoken.v1beta1.MsgSetUri` | 61 | | bitsong.fantoken.v1beta1.EventSetUri | denom | {denom} | 62 | -------------------------------------------------------------------------------- /x/fantoken/spec/05_parameters.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Parameters 6 | 7 | Fantoken module parameters. 8 | 9 | | Key | Type | Value | 10 | | ---------- | -------- | --------------------------------------- | 11 | | IssueFee | sdk.Coin | {"denom": "ubtsg", "amount": "1000000"} | 12 | | MintFee | sdk.Coin | {"denom": "ubtsg", "amount": "0"} | 13 | | BurnFee | sdk.Coin | {"denom": "ubtsg", "amount": "0"} | -------------------------------------------------------------------------------- /x/fantoken/spec/06_client.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Client 6 | 7 | ## Transactions 8 | 9 | The `transactions` commands allow users to `issue`, `mint`, `burn`, `disable minting`, `transfer minting and editing capabilities` for _fan tokens_. 10 | 11 | ```bash= 12 | bitsongd tx fantoken --help 13 | ``` 14 | ### issue 15 | 16 | ```bash= 17 | bitsongd tx fantoken issue \ 18 | --name "fantoken name" \ 19 | --symbol "bitangel" \ 20 | --max-supply 100000000000 \ 21 | --uri "ipfs://...." \ 22 | --from -b block --chain-id --fees 23 | ``` 24 | 25 | ### mint 26 | 27 | ```bash= 28 | bitsongd tx fantoken mint [amount][denom] \ 29 | --recipient
\ 30 | --from -b block --chain-id --fees 31 | ``` 32 | 33 | ### burn 34 | 35 | ```bash= 36 | bitsongd tx fantoken burn [amount][denom] \ 37 | --from -b block --chain-id --fees 38 | ``` 39 | 40 | ### set-authority 41 | 42 | ```bash= 43 | bitsongd tx fantoken set-authority [denom] \ 44 | --new-authority
\ 45 | --from -b block --chain-id --fees 46 | ``` 47 | 48 | ### set-minter 49 | 50 | ```bash= 51 | bitsongd tx fantoken set-minter [denom] \ 52 | --new-minter
\ 53 | --from -b block --chain-id --fees 54 | ``` 55 | 56 | ### set-uri 57 | 58 | ```bash= 59 | bitsongd tx fantoken set-uri [denom] \ 60 | --uri \ 61 | --from -b block --chain-id --fees 62 | ``` 63 | 64 | ### disable-mint 65 | 66 | ```bash= 67 | bitsongd tx fantoken disable-mint [denom] \ 68 | --from -b block --chain-id --fees 69 | ``` 70 | 71 | ## Query 72 | 73 | The `query` commands allow users to query the `fantoken` module. 74 | 75 | ```bash= 76 | bitsongd q fantoken --help 77 | ``` 78 | 79 | ### denom 80 | 81 | ```bash= 82 | bitsongd q fantoken denom 83 | ``` 84 | 85 | ### authority 86 | 87 | ```bash= 88 | bitsongd q fantoken authority
89 | ``` 90 | 91 | ### params 92 | 93 | ```bash= 94 | bitsongd q fantoken params 95 | ``` -------------------------------------------------------------------------------- /x/fantoken/spec/07_future_improvements.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Future Improvements 6 | -------------------------------------------------------------------------------- /x/fantoken/spec/img/fantoken_instance_lifecycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | finite_state_machine 11 | 12 | 13 | 1 14 | 15 | 1 16 | 17 | 18 | 19 | 1->1 20 | 21 | 22 | trading 23 | 24 | 25 | 26 | 2 27 | 28 | 2 29 | 30 | 31 | 32 | 1->2 33 | 34 | 35 | burn 36 | 37 | 38 | 39 | qi 40 | 41 | 42 | 43 | 44 | qi->1 45 | 46 | 47 | minting 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /x/fantoken/spec/img/image_code/fantoken_instance_lifecycle.dot: -------------------------------------------------------------------------------- 1 | digraph finite_state_machine { 2 | rankdir=LR; 3 | bgcolor="transparent"; 4 | size="8,5" 5 | 6 | node [shape = circle, label="1", fontsize=14, color="gray", fontcolor="gray"] 1; 7 | node [shape = circle, label="2", fontsize=14, color="gray", fontcolor="gray"] 2; 8 | 9 | node [shape = point, fontsize=14, color="gray", fontcolor="gray"]; qi 10 | 11 | qi -> 1 [label="minting", fontsize=14, color="gray", fontcolor="gray"]; 12 | 13 | 1 -> 1 [ label = "trading", fontsize=14, color="gray", fontcolor="gray"]; 14 | 1 -> 2 [ label = "burn", fontsize=14, color="gray", fontcolor="gray"]; 15 | } -------------------------------------------------------------------------------- /x/fantoken/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 10 | ) 11 | 12 | var ( 13 | amino = codec.NewLegacyAmino() 14 | ModuleCdc = codec.NewAminoCodec(amino) 15 | ) 16 | 17 | func init() { 18 | RegisterLegacyAminoCodec(amino) 19 | cryptocodec.RegisterCrypto(amino) 20 | amino.Seal() 21 | } 22 | 23 | func RegisterInterfaces(registry types.InterfaceRegistry) { 24 | registry.RegisterImplementations((*sdk.Msg)(nil), 25 | &MsgIssue{}, 26 | &MsgMint{}, 27 | &MsgBurn{}, 28 | &MsgDisableMint{}, 29 | &MsgSetAuthority{}, 30 | &MsgSetMinter{}, 31 | &MsgSetUri{}, 32 | ) 33 | 34 | registry.RegisterImplementations( 35 | (*v1beta1.Content)(nil), 36 | &UpdateFeesProposal{}, 37 | ) 38 | 39 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 40 | } 41 | 42 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 43 | cdc.RegisterConcrete(&MsgIssue{}, "go-bitsong/fantoken/MsgIssue", nil) 44 | cdc.RegisterConcrete(&MsgMint{}, "go-bitsong/fantoken/MsgMint", nil) 45 | cdc.RegisterConcrete(&MsgBurn{}, "go-bitsong/fantoken/MsgBurn", nil) 46 | cdc.RegisterConcrete(&MsgDisableMint{}, "go-bitsong/fantoken/MsgDisableMint", nil) 47 | cdc.RegisterConcrete(&MsgSetAuthority{}, "go-bitsong/fantoken/MsgSetAuthority", nil) 48 | cdc.RegisterConcrete(&MsgSetMinter{}, "go-bitsong/fantoken/MsgSetMinter", nil) 49 | cdc.RegisterConcrete(&MsgSetUri{}, "go-bitsong/fantoken/MsgSetUri", nil) 50 | cdc.RegisterConcrete(&UpdateFeesProposal{}, "go-bitsong/fantoken/UpdateFeesProposal", nil) 51 | } 52 | -------------------------------------------------------------------------------- /x/fantoken/types/errors.go: -------------------------------------------------------------------------------- 1 | // nolint 2 | package types 3 | 4 | import ( 5 | sdkerrors "cosmossdk.io/errors" 6 | ) 7 | 8 | // fantoken module errors 9 | var ( 10 | ErrInvalidName = sdkerrors.Register(ModuleName, 1, "invalid fantoken name") 11 | ErrInvalidDenom = sdkerrors.Register(ModuleName, 2, "invalid fantoken denom") 12 | ErrInvalidSymbol = sdkerrors.Register(ModuleName, 3, "invalid standard symbol") 13 | ErrInvalidMaxSupply = sdkerrors.Register(ModuleName, 4, "invalid fantoken maximum supply") 14 | ErrDenomAlreadyExists = sdkerrors.Register(ModuleName, 5, "denom already exists") 15 | ErrFanTokenNotExists = sdkerrors.Register(ModuleName, 6, "fantoken does not exist") 16 | ErrInvalidToAddress = sdkerrors.Register(ModuleName, 7, "the new owner must not be same as the original owner") 17 | ErrInvalidAuthority = sdkerrors.Register(ModuleName, 8, "invalid fantoken authority") 18 | ErrInvalidMinter = sdkerrors.Register(ModuleName, 9, "invalid fantoken minter") 19 | ErrInvalidRecipient = sdkerrors.Register(ModuleName, 10, "invalid fantoken recipient") 20 | ErrInvalidOwner = sdkerrors.Register(ModuleName, 11, "the owner is empty or invalid") 21 | ErrNotFoundTokenAmt = sdkerrors.Register(ModuleName, 12, "burned fantoken amount not found") 22 | ErrInvalidAmount = sdkerrors.Register(ModuleName, 13, "invalid amount") 23 | ErrInvalidUri = sdkerrors.Register(ModuleName, 14, "invalid uri length") 24 | ) 25 | -------------------------------------------------------------------------------- /x/fantoken/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | context "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" 8 | ) 9 | 10 | type ProtocolPoolKeeper interface { 11 | FundCommunityPool(ctx context.Context, amount sdk.Coins, sender sdk.AccAddress) error 12 | } 13 | 14 | // ParamSubspace defines the expected Subspace interface for parameters (noalias) 15 | type ParamSubspace interface { 16 | GetParamSet(ctx sdk.Context, ps paramstypes.ParamSet) 17 | SetParamSet(ctx sdk.Context, ps paramstypes.ParamSet) 18 | HasKeyTable() bool 19 | WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace 20 | } 21 | 22 | type AccountKeeper interface { 23 | GetModuleAddress(name string) sdk.AccAddress 24 | } 25 | 26 | // BankKeeper defines the expected bank keeper (noalias) 27 | type BankKeeper interface { 28 | MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error 29 | BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error 30 | GetSupply(ctx context.Context, denom string) sdk.Coin 31 | SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 32 | SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 33 | 34 | //GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin 35 | //SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error 36 | //SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 37 | } 38 | -------------------------------------------------------------------------------- /x/fantoken/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DefaultGenesisState returns the default genesis state for testing 4 | func DefaultGenesisState() *GenesisState { 5 | return &GenesisState{ 6 | Params: DefaultParams(), 7 | FanTokens: []FanToken{}, 8 | } 9 | } 10 | 11 | // NewGenesisState creates a new genesis state. 12 | func NewGenesisState(params Params, fantokens []FanToken) GenesisState { 13 | return GenesisState{ 14 | Params: params, 15 | FanTokens: fantokens, 16 | } 17 | } 18 | 19 | // Validate validates the provided token genesis state to ensure the 20 | // expected invariants holds. 21 | func (gs GenesisState) Validate() error { 22 | if err := gs.Params.Validate(); err != nil { 23 | return err 24 | } 25 | 26 | // validate fantoken 27 | for _, fantoken := range gs.FanTokens { 28 | if err := fantoken.ValidateWithDenom(); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /x/fantoken/types/gov.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 9 | ) 10 | 11 | const ProposalTypeUpdateFees = "UpdateFantokenFeesProposal" 12 | 13 | func init() { 14 | v1beta1.RegisterProposalType(ProposalTypeUpdateFees) 15 | // v1beta1.RegisterLegacyAminoCodec(&UpdateFeesProposal{}, "go-bitsong/fantoken/UpdateFeesProposal") 16 | } 17 | 18 | var _ v1beta1.Content = &UpdateFeesProposal{} 19 | 20 | func NewUpdateFeesProposal(title, description string, issueFee, mintFee, burnFee sdk.Coin) v1beta1.Content { 21 | return &UpdateFeesProposal{ 22 | Title: title, 23 | Description: description, 24 | IssueFee: issueFee, 25 | MintFee: mintFee, 26 | BurnFee: burnFee, 27 | } 28 | } 29 | 30 | func (p *UpdateFeesProposal) GetTitle() string { return p.Title } 31 | 32 | func (p *UpdateFeesProposal) GetDescription() string { return p.Description } 33 | 34 | func (p *UpdateFeesProposal) ProposalRoute() string { return RouterKey } 35 | 36 | func (p *UpdateFeesProposal) ProposalType() string { return ProposalTypeUpdateFees } 37 | 38 | func (p *UpdateFeesProposal) ValidateBasic() error { 39 | err := v1beta1.ValidateAbstract(p) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | if err := ValidateFees(p.IssueFee, p.MintFee, p.BurnFee); err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (p UpdateFeesProposal) String() string { 52 | var b strings.Builder 53 | b.WriteString(fmt.Sprintf(`Update Fantoken Fees Proposal: 54 | Title: %s 55 | Description: %s 56 | Issue Fee: %s 57 | Mint Fee: %s 58 | Burn Fee: %s 59 | `, p.Title, p.Description, p.IssueFee, p.MintFee, p.BurnFee)) 60 | return b.String() 61 | } 62 | -------------------------------------------------------------------------------- /x/fantoken/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | const ( 8 | // ModuleName is the name of the token module 9 | ModuleName = "fantoken" 10 | 11 | // StoreKey is the string store representation 12 | StoreKey string = ModuleName 13 | 14 | // RouterKey is the msg router key for the token module 15 | RouterKey string = ModuleName 16 | ) 17 | 18 | var ( 19 | // PrefixFanTokenForDenom defines a denom prefix for the fan token 20 | PrefixFanTokenForDenom = []byte{0x01} 21 | 22 | // PrefixFanTokens defines a prefix for the fan tokens 23 | PrefixFanTokens = []byte{0x02} 24 | ) 25 | 26 | // KeyDenom returns the key of the token with the specified denom 27 | func KeyDenom(denom string) []byte { 28 | return append(PrefixFanTokenForDenom, []byte(denom)...) 29 | } 30 | 31 | // KeyFanTokens returns the key of the specified owner and denom. Intended for querying all fan tokens of an owner 32 | func KeyFanTokens(owner sdk.AccAddress, denom string) []byte { 33 | return append(append(PrefixFanTokens, owner.Bytes()...), []byte(denom)...) 34 | } 35 | -------------------------------------------------------------------------------- /x/fantoken/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "cosmossdk.io/math" 7 | "gopkg.in/yaml.v2" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 11 | ) 12 | 13 | var _ paramtypes.ParamSet = (*Params)(nil) 14 | 15 | // parameter keys 16 | var ( 17 | KeyIssueFee = []byte("IssueFee") 18 | KeyMintFee = []byte("MintFee") 19 | KeyBurnFee = []byte("BurnFee") 20 | ) 21 | 22 | func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { 23 | return paramtypes.ParamSetPairs{ 24 | paramtypes.NewParamSetPair(KeyIssueFee, &p.IssueFee, validateFee), 25 | paramtypes.NewParamSetPair(KeyMintFee, &p.MintFee, validateFee), 26 | paramtypes.NewParamSetPair(KeyBurnFee, &p.BurnFee, validateFee), 27 | } 28 | } 29 | 30 | // NewParams constructs a new Params instance 31 | func NewParams(issueFee, mintFee, burnFee, transferFee sdk.Coin) Params { 32 | return Params{ 33 | IssueFee: issueFee, 34 | MintFee: mintFee, 35 | BurnFee: burnFee, 36 | } 37 | } 38 | 39 | // ParamKeyTable returns the TypeTable for the token module 40 | func ParamKeyTable() paramtypes.KeyTable { 41 | return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) 42 | } 43 | 44 | // DefaultParams return the default params 45 | func DefaultParams() Params { 46 | return Params{ 47 | IssueFee: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(1_000_000_000)), 48 | MintFee: sdk.NewCoin(sdk.DefaultBondDenom, math.ZeroInt()), 49 | BurnFee: sdk.NewCoin(sdk.DefaultBondDenom, math.ZeroInt()), 50 | } 51 | } 52 | 53 | // String returns a human readable string representation of the parameters. 54 | func (p Params) String() string { 55 | out, _ := yaml.Marshal(p) 56 | return string(out) 57 | } 58 | 59 | // Validate validates the given params 60 | func (p Params) Validate() error { 61 | if err := validateFee(p.IssueFee); err != nil { 62 | return err 63 | } 64 | 65 | if err := validateFee(p.MintFee); err != nil { 66 | return err 67 | } 68 | 69 | if err := validateFee(p.BurnFee); err != nil { 70 | return err 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func validateFee(i interface{}) error { 77 | v, ok := i.(sdk.Coin) 78 | if !ok { 79 | return fmt.Errorf("invalid parameter type: %T", i) 80 | } 81 | if v.IsNegative() { 82 | return fmt.Errorf("fee should not be negative") 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /x/fantoken/types/utils.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | tmcrypto "github.com/cometbft/cometbft/crypto" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | func GetFantokenDenom(height int64, minter sdk.AccAddress, symbol, name string) string { 11 | bz := []byte(fmt.Sprintf("%d%s%s%s", height, minter.String(), symbol, name)) 12 | return "ft" + tmcrypto.AddressHash(bz).String() 13 | } 14 | -------------------------------------------------------------------------------- /x/smart-account/authenticator/replay_protection.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 7 | 8 | errorsmod "cosmossdk.io/errors" 9 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 10 | ) 11 | 12 | // make replay protection into an interface. SequenceMatch is a default implementation 13 | type ReplayProtection func(txData *ExplicitTxData, signature *signing.SignatureV2) error 14 | 15 | func SequenceMatch(txData *ExplicitTxData, signature *signing.SignatureV2) error { 16 | if signature.Sequence != txData.AccountSequence { 17 | return errorsmod.Wrap( 18 | sdkerrors.ErrInvalidSequence, 19 | fmt.Sprintf("account sequence mismatch, expected %d, got %d", txData.AccountSequence, signature.Sequence), 20 | ) 21 | } 22 | return nil 23 | } 24 | 25 | func NoReplayProtection(txData *ExplicitTxData, signature *signing.SignatureV2) error { 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /x/smart-account/authenticator/requests.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | type TrackRequest struct { 8 | AuthenticatorId string `json:"authenticator_id"` 9 | Account sdk.AccAddress `json:"account"` 10 | FeePayer sdk.AccAddress `json:"fee_payer"` 11 | FeeGranter sdk.AccAddress `json:"fee_granter,omitempty"` 12 | Fee sdk.Coins `json:"fee"` 13 | Msg LocalAny `json:"msg"` 14 | MsgIndex uint64 `json:"msg_index"` 15 | AuthenticatorParams []byte `json:"authenticator_params,omitempty"` 16 | } 17 | 18 | type ConfirmExecutionRequest struct { 19 | AuthenticatorId string `json:"authenticator_id"` 20 | Account sdk.AccAddress `json:"account"` 21 | FeePayer sdk.AccAddress `json:"fee_payer"` 22 | FeeGranter sdk.AccAddress `json:"fee_granter,omitempty"` 23 | Fee sdk.Coins `json:"fee"` 24 | Msg LocalAny `json:"msg"` 25 | MsgIndex uint64 `json:"msg_index"` 26 | AuthenticatorParams []byte `json:"authenticator_params,omitempty"` 27 | } 28 | 29 | type AuthenticationRequest struct { 30 | AuthenticatorId string `json:"authenticator_id"` 31 | Account sdk.AccAddress `json:"account"` 32 | FeePayer sdk.AccAddress `json:"fee_payer"` 33 | FeeGranter sdk.AccAddress `json:"fee_granter,omitempty"` 34 | Fee sdk.Coins `json:"fee"` 35 | Msg LocalAny `json:"msg"` 36 | 37 | // Since array size is int, and size depends on the system architecture, 38 | // we use uint64 to cover all available architectures. 39 | // It is unsigned, so at this point, it can't be negative. 40 | MsgIndex uint64 `json:"msg_index"` 41 | 42 | // Only allowing messages with a single signer, so the signature can be a single byte array. 43 | Signature []byte `json:"signature"` 44 | SignModeTxData SignModeData `json:"sign_mode_tx_data"` 45 | TxData ExplicitTxData `json:"tx_data"` 46 | SignatureData SimplifiedSignatureData `json:"signature_data"` 47 | Simulate bool `json:"simulate"` 48 | AuthenticatorParams []byte `json:"authenticator_params,omitempty"` 49 | } 50 | -------------------------------------------------------------------------------- /x/smart-account/client/cli/query.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/bitsongofficial/go-bitsong/btsgutils/btsgcli" 5 | "github.com/spf13/cobra" 6 | 7 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 8 | // "github.com/bitsongofficial/go-bitsong/btsgutils/btsgcli" 9 | ) 10 | 11 | func GetQueryCmd() *cobra.Command { 12 | cmd := btsgcli.QueryIndexCmd(types.ModuleName) 13 | btsgcli.AddQueryCmd(cmd, types.NewQueryClient, GetCmdAuthenticators) 14 | btsgcli.AddQueryCmd(cmd, types.NewQueryClient, GetCmdAuthenticator) 15 | btsgcli.AddQueryCmd(cmd, types.NewQueryClient, GetCmdParams) 16 | 17 | return cmd 18 | } 19 | 20 | func GetCmdAuthenticators() (*btsgcli.QueryDescriptor, *types.GetAuthenticatorsRequest) { 21 | return &btsgcli.QueryDescriptor{ 22 | Use: "authenticators", 23 | Short: "Query authenticators by account", 24 | Long: `{{.Short}}{{.ExampleHeader}} 25 | {{.CommandPrefix}} bitsong12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj`, 26 | }, &types.GetAuthenticatorsRequest{} 27 | } 28 | 29 | func GetCmdAuthenticator() (*btsgcli.QueryDescriptor, *types.GetAuthenticatorRequest) { 30 | return &btsgcli.QueryDescriptor{ 31 | Use: "authenticator", 32 | Short: "Query authenticator by account and id", 33 | Long: `{{.Short}}{{.ExampleHeader}} 34 | {{.CommandPrefix}} bitsong12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj 17`, 35 | }, &types.GetAuthenticatorRequest{} 36 | } 37 | 38 | func GetCmdParams() (*btsgcli.QueryDescriptor, *types.QueryParamsRequest) { 39 | return &btsgcli.QueryDescriptor{ 40 | Use: "params", 41 | Short: "Query smartaccount params", 42 | Long: `{{.Short}}{{.ExampleHeader}} 43 | {{.CommandPrefix}} params`, 44 | }, &types.QueryParamsRequest{} 45 | } 46 | -------------------------------------------------------------------------------- /x/smart-account/client/cli/tx.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/pflag" 8 | 9 | "github.com/cosmos/cosmos-sdk/client" 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | 12 | "github.com/bitsongofficial/go-bitsong/btsgutils/btsgcli" 13 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 14 | ) 15 | 16 | func NewTxCmd() *cobra.Command { 17 | txCmd := btsgcli.TxIndexCmd(types.ModuleName) 18 | btsgcli.AddTxCmd(txCmd, NewAddAuthentiactorCmd) 19 | btsgcli.AddTxCmd(txCmd, NewRemoveAuthentiactorCmd) 20 | return txCmd 21 | } 22 | 23 | func NewAddAuthentiactorCmd() (*btsgcli.TxCliDesc, *types.MsgAddAuthenticator) { 24 | return &btsgcli.TxCliDesc{ 25 | Use: "add-authenticator", 26 | Short: "add an authenticator for an address", 27 | Long: "", 28 | Example: ` 29 | osmosisd tx authenticator add-authenticator SigVerification --from val \ 30 | --chain-id osmosis-1 -b sync --keyring-backend test \ 31 | --fees 1000uosmo 32 | `, 33 | ParseAndBuildMsg: BuildAddAuthenticatorMsg, 34 | }, &types.MsgAddAuthenticator{} 35 | } 36 | 37 | func NewRemoveAuthentiactorCmd() (*btsgcli.TxCliDesc, *types.MsgRemoveAuthenticator) { 38 | return &btsgcli.TxCliDesc{ 39 | Use: "remove-authenticator", 40 | Short: "add an authenticator for an address", 41 | Long: "", 42 | Example: ` 43 | osmosisd tx authenticator remove-authenticator 1 --from val \ 44 | --chain-id osmosis-1 -b sync --keyring-backend test \ 45 | --fees 1000uosmo 46 | `, 47 | }, &types.MsgRemoveAuthenticator{} 48 | } 49 | 50 | func BuildAddAuthenticatorMsg( 51 | clientCtx client.Context, 52 | args []string, 53 | flags *pflag.FlagSet, 54 | ) (sdk.Msg, error) { 55 | authenticatorType := args[0] 56 | pubKeyEncoded := args[1] 57 | 58 | pubKeyBytes, err := base64.StdEncoding.DecodeString(pubKeyEncoded) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return &types.MsgAddAuthenticator{ 64 | AuthenticatorType: authenticatorType, 65 | Data: pubKeyBytes, 66 | Sender: clientCtx.GetFromAddress().String(), 67 | }, nil 68 | } 69 | -------------------------------------------------------------------------------- /x/smart-account/genesis.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/bitsongofficial/go-bitsong/x/smart-account/keeper" 7 | "github.com/bitsongofficial/go-bitsong/x/smart-account/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 | k.SetParams(ctx, genState.Params) 13 | 14 | // if there is no NextAuthenticatorId set in genstate, go will set it to 0 15 | // we need to set it to FirstAuthenticatorId in that case 16 | if genState.NextAuthenticatorId == 0 { 17 | genState.NextAuthenticatorId = keeper.FirstAuthenticatorId 18 | } 19 | 20 | k.SetNextAuthenticatorId(ctx, genState.NextAuthenticatorId) 21 | 22 | for i := range genState.AuthenticatorData { 23 | accAddr, err := sdk.AccAddressFromBech32(genState.AuthenticatorData[i].Address) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | for j := range genState.AuthenticatorData[i].Authenticators { 29 | err := k.AddAuthenticatorWithId( 30 | ctx, 31 | accAddr, 32 | genState.AuthenticatorData[i].Authenticators[j].Type, 33 | genState.AuthenticatorData[i].Authenticators[j].Config, 34 | genState.AuthenticatorData[i].Authenticators[j].Id, 35 | ) 36 | if err != nil { 37 | panic(err) 38 | } 39 | } 40 | } 41 | } 42 | 43 | // ExportGenesis returns the module's exported genesis 44 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 45 | genesis := types.DefaultGenesis() 46 | genesis.Params = k.GetParams(ctx) 47 | 48 | genesis.NextAuthenticatorId = k.InitializeOrGetNextAuthenticatorId(ctx) 49 | allAuthenticators, err := k.GetAllAuthenticatorData(ctx) 50 | if err != nil { 51 | panic(err) 52 | } 53 | genesis.AuthenticatorData = allAuthenticators 54 | 55 | return genesis 56 | } 57 | -------------------------------------------------------------------------------- /x/smart-account/images/authentication_flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/images/authentication_flow.jpg -------------------------------------------------------------------------------- /x/smart-account/images/authenticator_manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/images/authenticator_manager.jpg -------------------------------------------------------------------------------- /x/smart-account/images/circuit_breaker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/images/circuit_breaker.jpg -------------------------------------------------------------------------------- /x/smart-account/images/keeper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/images/keeper.jpg -------------------------------------------------------------------------------- /x/smart-account/keeper/genesis.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | 10 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 11 | 12 | storetypes "cosmossdk.io/store/types" 13 | ) 14 | 15 | // GetAllAuthenticatorData is used in genesis export to export all the authenticator for all accounts 16 | func (k Keeper) GetAllAuthenticatorData(ctx sdk.Context) ([]types.AuthenticatorData, error) { 17 | var accountAuthenticators []types.AuthenticatorData 18 | 19 | parse := func(key []byte, value []byte) error { 20 | var authenticator types.AccountAuthenticator 21 | err := k.cdc.Unmarshal(value, &authenticator) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | // Extract account address from key 27 | accountAddr := strings.Split(string(key), "|")[1] 28 | 29 | // Check if this entry is for a new address or the same as the last one processed 30 | if len(accountAuthenticators) == 0 || 31 | accountAuthenticators[len(accountAuthenticators)-1].Address != accountAddr { 32 | // If it's a new address, create a new AuthenticatorData entry 33 | accountAuthenticators = append(accountAuthenticators, types.AuthenticatorData{ 34 | Address: accountAddr, 35 | Authenticators: []types.AccountAuthenticator{authenticator}, 36 | }) 37 | } else { 38 | // If it's the same address, append the authenticator to the last entry in the list 39 | lastIndex := len(accountAuthenticators) - 1 40 | accountAuthenticators[lastIndex].Authenticators = append(accountAuthenticators[lastIndex].Authenticators, authenticator) 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // Iterate over all entries in the store using a prefix iterator 47 | iterator := storetypes.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.KeyAccountAuthenticatorsPrefixId()) 48 | defer iterator.Close() 49 | 50 | for ; iterator.Valid(); iterator.Next() { 51 | err := parse(iterator.Key(), iterator.Value()) 52 | if err != nil { 53 | return nil, err 54 | } 55 | } 56 | 57 | return accountAuthenticators, nil 58 | } 59 | 60 | // AddAuthenticatorWithId adds an authenticator to an account, this function is used in genesis import 61 | func (k Keeper) AddAuthenticatorWithId(ctx sdk.Context, account sdk.AccAddress, authenticatorType string, config []byte, id uint64) error { 62 | impl := k.AuthenticatorManager.GetAuthenticatorByType(authenticatorType) 63 | if impl == nil { 64 | return fmt.Errorf("authenticator type %s is not registered", authenticatorType) 65 | } 66 | cacheCtx, _ := ctx.CacheContext() 67 | err := impl.OnAuthenticatorAdded(cacheCtx, account, config, strconv.FormatUint(id, 10)) 68 | if err != nil { 69 | return err 70 | } 71 | types.MustSet(ctx.KVStore(k.storeKey), 72 | types.KeyAccountId(account, id), 73 | &types.AccountAuthenticator{ 74 | Id: id, 75 | Type: authenticatorType, 76 | Config: config, 77 | }) 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /x/smart-account/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 10 | ) 11 | 12 | // GetParams get all parameters as types.Params 13 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { 14 | k.paramSpace.GetParamSet(ctx, ¶ms) 15 | return params 16 | } 17 | 18 | // SetParams set the params 19 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { 20 | k.paramSpace.SetParamSet(ctx, ¶ms) 21 | } 22 | 23 | // GetIsSmartAccountActive returns the value of the isSmartAccountActive parameter. 24 | // If the value has not been set, it will return false. 25 | // If there is an error unmarshalling the value, it will return false. 26 | func (k *Keeper) GetIsSmartAccountActive(ctx sdk.Context) bool { 27 | isSmartAccountActiveBz := k.paramSpace.GetRaw(ctx, types.KeyIsSmartAccountActive) 28 | if !bytes.Equal(isSmartAccountActiveBz, k.isSmartAccountActiveBz) { 29 | var isSmartAccountActiveValue bool 30 | err := json.Unmarshal(isSmartAccountActiveBz, &isSmartAccountActiveValue) 31 | if err != nil { 32 | k.Logger(ctx).Error("failed to unmarshal isSmartAccountActive", "error", err) 33 | isSmartAccountActiveValue = false 34 | } 35 | k.isSmartAccountActiveVal = isSmartAccountActiveValue 36 | k.isSmartAccountActiveBz = isSmartAccountActiveBz 37 | } 38 | return k.isSmartAccountActiveVal 39 | } 40 | -------------------------------------------------------------------------------- /x/smart-account/keeper/query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | 10 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 11 | ) 12 | 13 | var _ types.QueryServer = Keeper{} 14 | 15 | func (k Keeper) GetAuthenticators( 16 | ctx context.Context, 17 | request *types.GetAuthenticatorsRequest, 18 | ) (*types.GetAuthenticatorsResponse, error) { 19 | if request == nil { 20 | return nil, status.Error(codes.InvalidArgument, "empty request") 21 | } 22 | 23 | sdkCtx := sdk.UnwrapSDKContext(ctx) 24 | acc, err := sdk.AccAddressFromBech32(request.Account) 25 | if err != nil { 26 | return nil, status.Error(codes.Internal, err.Error()) 27 | } 28 | 29 | authenticators, err := k.GetAuthenticatorDataForAccount(sdkCtx, acc) 30 | if err != nil { 31 | return nil, status.Error(codes.Internal, err.Error()) 32 | } 33 | 34 | return &types.GetAuthenticatorsResponse{AccountAuthenticators: authenticators}, nil 35 | } 36 | 37 | func (k Keeper) GetAuthenticator( 38 | ctx context.Context, 39 | request *types.GetAuthenticatorRequest, 40 | ) (*types.GetAuthenticatorResponse, error) { 41 | if request == nil { 42 | return nil, status.Error(codes.InvalidArgument, "empty request") 43 | } 44 | 45 | sdkCtx := sdk.UnwrapSDKContext(ctx) 46 | acc, err := sdk.AccAddressFromBech32(request.Account) 47 | if err != nil { 48 | return nil, status.Error(codes.Internal, err.Error()) 49 | } 50 | 51 | authenticator, err := k.GetSelectedAuthenticatorData(sdkCtx, acc, int(request.AuthenticatorId)) 52 | if err != nil { 53 | return nil, status.Error(codes.Internal, err.Error()) 54 | } 55 | 56 | return &types.GetAuthenticatorResponse{AccountAuthenticator: authenticator}, nil 57 | } 58 | -------------------------------------------------------------------------------- /x/smart-account/keeper/query_params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | 10 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 11 | ) 12 | 13 | func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { 14 | if req == nil { 15 | return nil, status.Error(codes.InvalidArgument, "invalid request") 16 | } 17 | ctx := sdk.UnwrapSDKContext(goCtx) 18 | 19 | return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil 20 | } 21 | -------------------------------------------------------------------------------- /x/smart-account/simulation/helpers.go: -------------------------------------------------------------------------------- 1 | package simulation 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 6 | ) 7 | 8 | // FindAccount finds a specific address from an account list 9 | func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { 10 | creator, err := sdk.AccAddressFromBech32(address) 11 | if err != nil { 12 | panic(err) 13 | } 14 | return simtypes.FindAccount(accs, creator) 15 | } 16 | -------------------------------------------------------------------------------- /x/smart-account/testutils/bytecode/cosigner_authenticator.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/testutils/bytecode/cosigner_authenticator.wasm -------------------------------------------------------------------------------- /x/smart-account/testutils/bytecode/echo.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/testutils/bytecode/echo.wasm -------------------------------------------------------------------------------- /x/smart-account/testutils/bytecode/spend_limit_v1.0.0-alpha.1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsongofficial/go-bitsong/cf7a58c6f3c274e9153f0f907ebfa8b88e58cf97/x/smart-account/testutils/bytecode/spend_limit_v1.0.0-alpha.1.wasm -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 6 | 7 | # for all dirs in SCRIPT_DIR 8 | for dir in $SCRIPT_DIR/*; do 9 | # if dir is a directory 10 | if [ -d "$dir" ]; then 11 | # if dir has a Cargo.toml 12 | if [ -f "$dir/Cargo.toml" ]; then 13 | # build the contract 14 | pushd $dir 15 | cargo wasm 16 | cp target/wasm32-unknown-unknown/release/*.wasm "$SCRIPT_DIR/../bytecode/" 17 | popd 18 | fi 19 | fi 20 | done 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --target wasm32-unknown-unknown --release --lib" 3 | wasm-debug = "build --target wasm32-unknown-unknown --lib" 4 | 5 | [build] 6 | rustflags = ["-C", "link-arg=-s"] 7 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "cosigner-authenticator" 4 | version = "0.1.0" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | cosmwasm-schema = "1.3.1" 11 | cosmwasm-std = {version = "1.5"} 12 | cw-storage-plus = "1.1.0" 13 | osmosis-authenticators = "0.22.0-alpha.3" 14 | schemars = "0.8.12" 15 | serde = "1.0.180" 16 | serde-json-wasm = "1.0.0" 17 | sylvia = "0.9.2" 18 | 19 | [dev-dependencies] 20 | secp256k1 = {version = "0.28.0", features = ["hashes-std"]} 21 | sylvia = {version = "0.9.2", features = ["mt"]} 22 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod types; 3 | 4 | #[cfg(any(test, feature = "tests"))] 5 | pub mod multitest; 6 | #[cfg(any(test, feature = "tests"))] 7 | mod test_utils; 8 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Binary; 2 | use secp256k1::hashes::sha256; 3 | use secp256k1::{Message, Secp256k1}; 4 | use secp256k1::{PublicKey, SecretKey}; 5 | 6 | /// Generates a Secp256k1 private/public key pair. 7 | fn generate_keypair(hex_secret: &[u8]) -> (SecretKey, PublicKey) { 8 | let secp = Secp256k1::new(); 9 | //let secret_key = SecretKey::from_str(randomness).unwrap(); 10 | let secret_key = SecretKey::from_slice(&hex_secret).unwrap(); 11 | let public_key = PublicKey::from_secret_key(&secp, &secret_key); 12 | (secret_key, public_key) 13 | } 14 | 15 | /// Signs a message with a given private key. 16 | pub fn sign_message(priv_key: &SecretKey, message: &str) -> Vec { 17 | let secp = Secp256k1::new(); 18 | let message = Message::from_hashed_data::(message.as_bytes()); 19 | //let message = Message::from_slice(message).expect("32 bytes"); 20 | let signature = secp.sign_ecdsa(&message, priv_key); 21 | signature.serialize_compact().to_vec() 22 | } 23 | 24 | pub fn generate_keys_and_sign(hex_secret: &[u8], message: &str) -> (SecretKey, Binary, Binary) { 25 | let (priv_key, pub_key) = generate_keypair(hex_secret); 26 | let signature = sign_message(&priv_key, message); 27 | let pubkey_binary = Binary::from(pub_key.serialize().as_ref()); 28 | let signature_binary = Binary::from(signature.as_slice()); 29 | 30 | (priv_key, pubkey_binary, signature_binary) 31 | } 32 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/src/tests.rs: -------------------------------------------------------------------------------- 1 | use sylvia::multitest::App; 2 | 3 | use crate::contract::multitest_utils::CodeId; 4 | 5 | #[test] 6 | fn instantiate() { 7 | let app = App::default(); 8 | let code_id = CodeId::store_code(&app); 9 | 10 | let owner = "owner"; 11 | 12 | let contract = code_id.instantiate(42).call(owner).unwrap(); 13 | 14 | let count = contract.count().unwrap().count; 15 | assert_eq!(count, 42); 16 | } 17 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/cosigner-authenticator/src/types.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::Binary; 3 | 4 | #[cw_serde] 5 | pub struct PubkeysResponse { 6 | pub pubkeys: Vec, 7 | } 8 | 9 | #[cw_serde] 10 | pub struct Signature { 11 | pub salt: Binary, 12 | pub signature: Binary, 13 | } 14 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/echo/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --target wasm32-unknown-unknown --release --lib" 3 | wasm-debug = "build --target wasm32-unknown-unknown --lib" 4 | 5 | [build] 6 | rustflags = ["-C", "link-arg=-s"] 7 | -------------------------------------------------------------------------------- /x/smart-account/testutils/contracts/echo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["osmosis contributors"] 3 | description = "Cosmwasm contract that always returns the same response" 4 | edition = "2021" 5 | name = "echo" 6 | version = "0.1.0" 7 | 8 | exclude = [ 9 | # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. 10 | "contract.wasm", 11 | "hash.txt", 12 | ] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | crate-type = ["cdylib", "rlib"] 18 | 19 | [features] 20 | # for more explicit tests, cargo test --features=backtraces 21 | backtraces = ["cosmwasm-std/backtraces"] 22 | # use library feature to disable all instantiate/execute/query exports 23 | library = [] 24 | 25 | [package.metadata.scripts] 26 | optimize = """docker run --rm -v "$(pwd)":/code \ 27 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 28 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 29 | cosmwasm/rust-optimizer:0.16.0 30 | """ 31 | 32 | [dependencies] 33 | cosmwasm-schema = "1.1.3" 34 | cosmwasm-std = { version = "1.5", features = ["stargate"] } 35 | cosmwasm-storage = "1.1.3" 36 | cw-storage-plus = "1.0.1" 37 | schemars = "0.8.10" 38 | serde = { version = "1.0.145", default-features = false, features = ["derive"] } 39 | #serde-cw-value = "0.7.0" 40 | osmosis-std = "0.20.1" 41 | thiserror = { version = "1.0.31" } 42 | #base64-simd = "0.8.0" 43 | osmosis-authenticators = "0.22.0-alpha.19" 44 | sha2 = "0.10.8" 45 | 46 | [dev-dependencies] 47 | serde-json-wasm = "1.0.0" 48 | -------------------------------------------------------------------------------- /x/smart-account/testutils/generic_authenticator.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | 6 | errorsmod "cosmossdk.io/errors" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | 10 | "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" 11 | ) 12 | 13 | var ( 14 | _ authenticator.Authenticator = &TestingAuthenticator{} 15 | ) 16 | 17 | type ApproveOn int 18 | 19 | const ( 20 | Always ApproveOn = iota 21 | Never 22 | ) 23 | 24 | type ( 25 | TestingAuthenticatorData struct{} 26 | TestingAuthenticator struct { 27 | Approve ApproveOn 28 | GasConsumption int 29 | BlockAddition bool 30 | BlockRemoval bool 31 | Confirm ApproveOn 32 | } 33 | ) 34 | 35 | func (t TestingAuthenticator) Type() string { 36 | var when string 37 | if t.Approve == Always { 38 | when = "Always" 39 | } else { 40 | when = "Never" 41 | } 42 | 43 | var confirm string 44 | if t.Confirm == Always { 45 | confirm = "Confirm" 46 | } else { 47 | confirm = "Block" 48 | } 49 | 50 | return "TestingAuthenticator" + when + confirm + fmt.Sprintf("GasConsumption%d", t.GasConsumption) + fmt.Sprintf("BlockAddition%t", t.BlockAddition) + fmt.Sprintf("BlockRemoval%t", t.BlockRemoval) 51 | } 52 | 53 | func (t TestingAuthenticator) StaticGas() uint64 { 54 | return uint64(t.GasConsumption) 55 | } 56 | 57 | func (t TestingAuthenticator) Initialize(config []byte) (authenticator.Authenticator, error) { 58 | return t, nil 59 | } 60 | 61 | func (t TestingAuthenticator) Authenticate(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 62 | if t.Approve == Always { 63 | return nil 64 | } else { 65 | return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "TestingAuthenticator authentication error") 66 | } 67 | } 68 | 69 | func (t TestingAuthenticator) Track(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 70 | return nil 71 | } 72 | 73 | func (t TestingAuthenticator) ConfirmExecution(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 74 | if t.Confirm == Always { 75 | return nil 76 | } else { 77 | return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "TestingAuthenticator block") 78 | } 79 | } 80 | 81 | func (t TestingAuthenticator) OnAuthenticatorAdded(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { 82 | if t.BlockAddition { 83 | return fmt.Errorf("authenticator could not be added") 84 | } 85 | return nil 86 | } 87 | 88 | func (t TestingAuthenticator) OnAuthenticatorRemoved(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { 89 | if t.BlockRemoval { 90 | return fmt.Errorf("authenticator could not be removed") 91 | } 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /x/smart-account/testutils/stateful_authenticator.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "cosmossdk.io/store/prefix" 8 | storetypes "cosmossdk.io/store/types" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | 11 | "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" 12 | ) 13 | 14 | var _ authenticator.Authenticator = &StatefulAuthenticator{} 15 | 16 | type StatefulAuthenticatorData struct { 17 | Value int 18 | } 19 | 20 | // StatefulAuthenticator is an experiment of how to write authenticators that handle state 21 | type StatefulAuthenticator struct { 22 | KvStoreKey storetypes.StoreKey 23 | } 24 | 25 | func (s StatefulAuthenticator) Type() string { 26 | return "Stateful" 27 | } 28 | 29 | func (s StatefulAuthenticator) StaticGas() uint64 { 30 | return 1000 31 | } 32 | 33 | func (s StatefulAuthenticator) Initialize(config []byte) (authenticator.Authenticator, error) { 34 | return s, nil 35 | } 36 | 37 | func (s StatefulAuthenticator) Authenticate(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 38 | statefulData := StatefulAuthenticatorData{Value: s.GetValue(ctx)} 39 | if statefulData.Value > 10 { 40 | return fmt.Errorf("Value is too high: %d", statefulData.Value) 41 | } 42 | s.SetValue(ctx, statefulData.Value+1) 43 | return nil 44 | } 45 | 46 | func (s StatefulAuthenticator) Track(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 47 | statefulData := StatefulAuthenticatorData{Value: s.GetValue(ctx)} 48 | if statefulData.Value > 10 { 49 | return fmt.Errorf("Value is too high: %d", statefulData.Value) 50 | } 51 | s.SetValue(ctx, statefulData.Value+1) 52 | return nil 53 | } 54 | 55 | func (s StatefulAuthenticator) SetValue(ctx sdk.Context, value int) { 56 | kvStore := prefix.NewStore(ctx.KVStore(s.KvStoreKey), []byte(s.Type())) 57 | statefulData := StatefulAuthenticatorData{Value: value} 58 | newBz, _ := json.Marshal(statefulData) 59 | kvStore.Set([]byte("value"), newBz) 60 | } 61 | 62 | func (s StatefulAuthenticator) GetValue(ctx sdk.Context) int { 63 | kvStore := prefix.NewStore(ctx.KVStore(s.KvStoreKey), []byte(s.Type())) 64 | bz := kvStore.Get([]byte("value")) // global value. On the real thing we may want the account 65 | var statefulData StatefulAuthenticatorData 66 | _ = json.Unmarshal(bz, &statefulData) // if we can't unmarshal, we just assume it's 0 67 | return statefulData.Value 68 | } 69 | 70 | func (s StatefulAuthenticator) ConfirmExecution(ctx sdk.Context, request authenticator.AuthenticationRequest) error { 71 | s.SetValue(ctx, s.GetValue(ctx)+1) 72 | return nil 73 | } 74 | 75 | func (s StatefulAuthenticator) OnAuthenticatorAdded(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { 76 | return nil 77 | } 78 | 79 | func (s StatefulAuthenticator) OnAuthenticatorRemoved(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /x/smart-account/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 | "github.com/cosmos/cosmos-sdk/types/msgservice" 7 | "github.com/cosmos/cosmos-sdk/types/tx" 8 | ) 9 | 10 | // AuthenticatorTxOptions 11 | type AuthenticatorTxOptions interface { 12 | GetSelectedAuthenticators() []uint64 13 | } 14 | 15 | func RegisterCodec(cdc *codec.LegacyAmino) { 16 | } 17 | 18 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { 19 | registry.RegisterImplementations((*tx.TxExtensionOptionI)(nil), &TxExtension{}) 20 | 21 | registry.RegisterImplementations( 22 | (*AuthenticatorTxOptions)(nil), 23 | &TxExtension{}, 24 | ) 25 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 26 | } 27 | 28 | var ( 29 | Amino = codec.NewLegacyAmino() 30 | ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) 31 | ) 32 | -------------------------------------------------------------------------------- /x/smart-account/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | context "context" 5 | 6 | "cosmossdk.io/x/feegrant" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | type ContractKeeper interface { 11 | Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) 12 | } 13 | 14 | // FeegrantKeeper defines the expected feegrant keeper. 15 | type FeegrantKeeper interface { 16 | GetAllowance(ctx context.Context, granter, grantee sdk.AccAddress) (feegrant.FeeAllowanceI, error) 17 | } 18 | -------------------------------------------------------------------------------- /x/smart-account/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DefaultIndex is the default global index 4 | const DefaultIndex uint64 = 0 5 | 6 | // DefaultGenesis returns the default genesis state 7 | func DefaultGenesis() *GenesisState { 8 | return &GenesisState{ 9 | Params: DefaultParams(), 10 | NextAuthenticatorId: DefaultIndex, 11 | AuthenticatorData: []AuthenticatorData{}, 12 | } 13 | } 14 | 15 | // Validate performs basic genesis state validation returning an error upon any 16 | // failure. 17 | func (gs GenesisState) Validate() error { 18 | return gs.Params.Validate() 19 | } 20 | -------------------------------------------------------------------------------- /x/smart-account/types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/bitsongofficial/go-bitsong/x/smart-account/types" 9 | ) 10 | 11 | func TestGenesisState_Validate(t *testing.T) { 12 | tests := []struct { 13 | desc string 14 | genState *types.GenesisState 15 | valid bool 16 | }{ 17 | { 18 | desc: "default is valid", 19 | genState: types.DefaultGenesis(), 20 | valid: true, 21 | }, 22 | { 23 | desc: "valid genesis state", 24 | genState: &types.GenesisState{}, 25 | valid: true, 26 | }, 27 | } 28 | for _, tc := range tests { 29 | t.Run(tc.desc, func(t *testing.T) { 30 | err := tc.genState.Validate() 31 | if tc.valid { 32 | require.NoError(t, err) 33 | } else { 34 | require.Error(t, err) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /x/smart-account/types/msgs.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | const ( 10 | TypeMsgAddAuthenticator = "add_authenticator" 11 | TypeMsgRemoveAuthenticator = "remove_authenticator" 12 | ) 13 | 14 | // Helper functions 15 | func validateSender(sender string) error { 16 | _, err := sdk.AccAddressFromBech32(sender) 17 | if err != nil { 18 | return fmt.Errorf("invalid sender address (%s)", err) 19 | } 20 | return nil 21 | } 22 | 23 | func getSender(sender string) []sdk.AccAddress { 24 | addr, err := sdk.AccAddressFromBech32(sender) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return []sdk.AccAddress{addr} 29 | } 30 | 31 | // MsgAddAuthenticator 32 | var _ sdk.Msg = &MsgAddAuthenticator{} 33 | 34 | func (msg *MsgAddAuthenticator) ValidateBasic() error { 35 | return validateSender(msg.Sender) 36 | } 37 | 38 | func (msg *MsgAddAuthenticator) GetSigners() []sdk.AccAddress { 39 | return getSender(msg.Sender) 40 | } 41 | 42 | func (msg MsgAddAuthenticator) GetSignBytes() []byte { 43 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) 44 | } 45 | func (msg MsgAddAuthenticator) Route() string { return RouterKey } 46 | 47 | func (msg MsgAddAuthenticator) Type() string { return TypeMsgAddAuthenticator } 48 | 49 | // MsgRemoveAuthenticator 50 | var _ sdk.Msg = &MsgRemoveAuthenticator{} 51 | 52 | func (msg *MsgRemoveAuthenticator) ValidateBasic() error { 53 | return validateSender(msg.Sender) 54 | } 55 | 56 | func (msg MsgRemoveAuthenticator) GetSignBytes() []byte { 57 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) 58 | } 59 | 60 | func (msg MsgRemoveAuthenticator) Route() string { return RouterKey } 61 | 62 | func (msg MsgRemoveAuthenticator) Type() string { return TypeMsgRemoveAuthenticator } 63 | 64 | func (msg *MsgRemoveAuthenticator) GetSigners() []sdk.AccAddress { 65 | return getSender(msg.Sender) 66 | } 67 | 68 | // MsgSetActiveState 69 | var _ sdk.Msg = &MsgSetActiveState{} 70 | 71 | func (msg *MsgSetActiveState) ValidateBasic() error { 72 | return validateSender(msg.Sender) 73 | } 74 | 75 | func (msg *MsgSetActiveState) GetSigners() []sdk.AccAddress { 76 | return getSender(msg.Sender) 77 | } 78 | -------------------------------------------------------------------------------- /x/smart-account/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 8 | ) 9 | 10 | var _ paramtypes.ParamSet = (*Params)(nil) 11 | 12 | // ParamKeyTable the param key table for launch module 13 | func ParamKeyTable() paramtypes.KeyTable { 14 | return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) 15 | } 16 | 17 | // NewParams creates a new Params instance 18 | func NewParams() Params { 19 | return Params{ 20 | MaximumUnauthenticatedGas: 120_000, 21 | IsSmartAccountActive: true, 22 | CircuitBreakerControllers: []string{}, 23 | } 24 | } 25 | 26 | // DefaultParams returns a default set of parameters 27 | func DefaultParams() Params { 28 | return NewParams() 29 | } 30 | 31 | // ParamSetPairs get the params.ParamSet 32 | func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { 33 | return paramtypes.ParamSetPairs{ 34 | paramtypes.NewParamSetPair(KeyMaximumUnauthenticatedGas, &p.MaximumUnauthenticatedGas, validateMaximumUnauthenticatedGas), 35 | paramtypes.NewParamSetPair(KeyIsSmartAccountActive, &p.IsSmartAccountActive, validateIsSmartAccountActive), 36 | paramtypes.NewParamSetPair(KeyCircuitBreakerControllers, &p.CircuitBreakerControllers, validateCircuitBreakerControllers), 37 | } 38 | } 39 | 40 | // Validate validates the set of params 41 | func (p Params) Validate() error { 42 | err := validateMaximumUnauthenticatedGas(p.MaximumUnauthenticatedGas) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | err = validateIsSmartAccountActive(p.IsSmartAccountActive) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | err = validateCircuitBreakerControllers(p.CircuitBreakerControllers) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | 60 | // Validate Default Gas Reduction 61 | func validateMaximumUnauthenticatedGas(i interface{}) error { 62 | // Convert the given parameter to a uint64. 63 | _, ok := i.(uint64) 64 | if !ok { 65 | return fmt.Errorf("invalid parameter type: %T", i) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func validateIsSmartAccountActive(i interface{}) error { 72 | // Convert the given parameter to a bool. 73 | _, ok := i.(bool) 74 | if !ok { 75 | return fmt.Errorf("invalid parameter type: %T", i) 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func validateCircuitBreakerControllers(i interface{}) error { 82 | // Convert the given parameter to a []string. 83 | controllers, ok := i.([]string) 84 | if !ok { 85 | return fmt.Errorf("invalid parameter type: %T", i) 86 | } 87 | 88 | // each string in the array should be a valid address 89 | for _, addr := range controllers { 90 | _, err := sdk.AccAddressFromBech32(addr) 91 | if err != nil { 92 | return fmt.Errorf("invalid address: %s", addr) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /x/smart-account/types/telemetry.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | CounterKeyMissingRegisteredAuthenticator = "authenticator_missing_registered_authenticator" 5 | CounterKeyTrackFailed = "authenticator_track_failed" 6 | 7 | MeasureKeyAnteHandler = "authenticator_ante_handle" 8 | MeasureKeyPostHandler = "authenticator_post_handle" 9 | 10 | GaugeKeyAnteHandlerGasConsumed = "authenticator_ante_handler_gas_consumed" 11 | GaugeKeyPostHandlerGasConsumed = "authenticator_post_handler_gas_consumed" 12 | ) 13 | --------------------------------------------------------------------------------