├── .gitattributes ├── client └── docs │ ├── swagger-ui │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.html │ └── oauth2-redirect.html │ ├── statik │ └── init.go │ └── config.json ├── CODEOWNERS ├── .github ├── CODEOWNERS ├── workflows │ ├── linkchecker.yml │ ├── lint.yml │ ├── test.yml │ └── sims.yml ├── .codecov.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md └── PULL_REQUEST_TEMPLATE.md ├── app ├── params │ ├── params.go │ ├── encoding.go │ ├── weights.go │ ├── doc.go │ ├── amino.go │ └── proto.go ├── encoding.go └── genesis.go ├── docs ├── Reference │ ├── proposals │ │ ├── README.md │ │ └── 01_proposal.md │ └── README.md ├── Tutorials │ ├── README.md │ └── localnet │ │ └── README.md ├── How-To │ └── README.md ├── README.md └── Explanation │ ├── ADR │ ├── README.md │ ├── adr-template.md │ └── PROCESS.md │ └── README.md ├── .pre-commit-config.yaml ├── third_party └── proto │ ├── tendermint │ ├── libs │ │ └── bits │ │ │ └── types.proto │ ├── crypto │ │ ├── keys.proto │ │ └── proof.proto │ ├── types │ │ ├── block.proto │ │ ├── validator.proto │ │ ├── evidence.proto │ │ └── params.proto │ ├── version │ │ └── types.proto │ └── p2p │ │ └── types.proto │ ├── cosmos_proto │ └── cosmos.proto │ ├── protoc-gen-openapiv2 │ └── options │ │ ├── BUILD.bazel │ │ └── annotations.proto │ ├── google │ └── api │ │ ├── annotations.proto │ │ └── httpbody.proto │ └── gogoproto │ └── gogo.proto ├── x └── farming │ ├── client │ ├── testutil │ │ ├── cli_test.go │ │ └── cli_helpers.go │ ├── proposal_handler.go │ ├── rest │ │ └── rest.go │ └── cli │ │ ├── flags.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── spec │ ├── README.md │ ├── 05_end_block.md │ ├── 07_params.md │ ├── 03_state_transitions.md │ ├── 01_concepts.md │ ├── 06_events.md │ ├── 08_proposal.md │ └── 04_messages.md │ ├── types │ ├── expected_keepers.go │ ├── utils.go │ ├── events.go │ ├── errors.go │ ├── params_test.go │ ├── codec.go │ ├── utils_test.go │ ├── params.go │ └── genesis.go │ ├── simulation │ ├── params_test.go │ ├── params.go │ ├── decoder.go │ ├── decoder_test.go │ ├── genesis_test.go │ ├── genesis.go │ └── proposals_test.go │ ├── abci.go │ ├── abci_test.go │ ├── keeper │ ├── epoch.go │ ├── keeper.go │ ├── epoch_test.go │ ├── msg_server.go │ └── proposal_handler.go │ ├── handler.go │ └── module_test.go ├── cmd └── farmingd │ └── main.go ├── contrib ├── githooks │ ├── README.md │ └── pre-commit └── devtools │ ├── Dockerfile │ └── Makefile ├── .gitignore ├── buf.yaml ├── CONTRIBUTING.md ├── scripts ├── protocgen.sh ├── protoc-swagger-gen.sh └── localnet.sh ├── go.mod ├── proto ├── cosmos │ └── base │ │ ├── v1beta1 │ │ └── coin.proto │ │ └── query │ │ └── v1beta1 │ │ └── pagination.proto └── tendermint │ └── farming │ └── v1beta1 │ └── proposal.proto ├── TECHNICAL-SETUP.md ├── README.md └── .clang-format /.gitattributes: -------------------------------------------------------------------------------- 1 | client/docs/swagger-ui/* linguist-vendored 2 | *.ts linguist-generated 3 | *.js linguist-generated 4 | -------------------------------------------------------------------------------- /client/docs/swagger-ui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tendermint/farming/HEAD/client/docs/swagger-ui/favicon-16x16.png -------------------------------------------------------------------------------- /client/docs/swagger-ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tendermint/farming/HEAD/client/docs/swagger-ui/favicon-32x32.png -------------------------------------------------------------------------------- /client/docs/statik/init.go: -------------------------------------------------------------------------------- 1 | package statik 2 | 3 | // This just for fixing the error in importing empty github.com/cosmos/cosmos-sdk/client/docs/statik 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | # Docs 4 | *.md @barriebyron 5 | 6 | # Primary repo maintainers 7 | * @dongsam @kogisin @hallazzang -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | # Docs 4 | *.md @barriebyron 5 | 6 | # Primary repo maintainers 7 | * @dongsam @kogisin @hallazzang 8 | -------------------------------------------------------------------------------- /app/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Simulation parameter constants 4 | const ( 5 | StakePerAccount = "stake_per_account" 6 | InitiallyBondedValidators = "initially_bonded_validators" 7 | ) 8 | -------------------------------------------------------------------------------- /docs/Reference/proposals/README.md: -------------------------------------------------------------------------------- 1 | # Proposals Documentation 2 | 3 | Use this location to record all signal governance proposals related to farming module. 4 | 5 | ## Contents 6 | 7 | - [Adopting the Budget and Farming Modules on Cosmos Hub](01_proposal.md) -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # commit msg check 3 | - repo: https://github.com/compilerla/conventional-pre-commit 4 | rev: v1.0.0 5 | hooks: 6 | - id: conventional-pre-commit 7 | stages: [commit-msg] 8 | args: [] # optional: list of Conventional Commits types to allow 9 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/libs/bits/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.libs.bits; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/libs/bits"; 5 | 6 | message BitArray { 7 | int64 bits = 1; 8 | repeated uint64 elems = 2; 9 | } 10 | -------------------------------------------------------------------------------- /x/farming/client/testutil/cli_test.go: -------------------------------------------------------------------------------- 1 | //go:build norace 2 | // +build norace 3 | 4 | package testutil 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | func TestIntegrationTestSuite(t *testing.T) { 13 | suite.Run(t, new(IntegrationTestSuite)) 14 | } 15 | 16 | func TestQueryCmdTestSuite(t *testing.T) { 17 | suite.Run(t, new(QueryCmdTestSuite)) 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/workflows/linkchecker.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - develop 9 | schedule: 10 | - cron: '* */24 * * *' 11 | 12 | jobs: 13 | markdown-link-check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@master 17 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.13 18 | with: 19 | folder-path: "." 20 | -------------------------------------------------------------------------------- /client/docs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Cosmos SDK Farming Module - REST and gRPC Gateway docs", 5 | "description": "A REST interface for state queries, transactions", 6 | "version": "1.0.0" 7 | }, 8 | "apis": [ 9 | { 10 | "url": "./tmp-swagger-gen/tendermint/farming/v1beta1/query.swagger.json", 11 | "operationIds": { 12 | "rename": { 13 | "Params": "FarmingParams" 14 | } 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/crypto/keys.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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 | -------------------------------------------------------------------------------- /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | # To validate: 2 | # cat codecov.yml | curl --data-binary @- https://codecov.io/validate 3 | 4 | codecov: 5 | notify: 6 | require_ci_to_pass: yes 7 | 8 | coverage: 9 | precision: 2 10 | round: down 11 | range: "50...80" 12 | 13 | status: 14 | project: 15 | default: 16 | target: auto 17 | threshold: 1% 18 | patch: 19 | default: 20 | enabled: no # disable patch since it is noisy and not correct 21 | if_not_found: success 22 | 23 | comment: false -------------------------------------------------------------------------------- /x/farming/client/proposal_handler.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | govclient "github.com/cosmos/cosmos-sdk/x/gov/client" 5 | 6 | "github.com/tendermint/farming/x/farming/client/cli" 7 | "github.com/tendermint/farming/x/farming/client/rest" 8 | ) 9 | 10 | // ProposalHandler is the public plan command handler. 11 | // Note that rest.ProposalRESTHandler will be deprecated in the future. 12 | var ( 13 | ProposalHandler = govclient.NewProposalHandler(cli.GetCmdSubmitPublicPlanProposal, rest.ProposalRESTHandler) 14 | ) 15 | -------------------------------------------------------------------------------- /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 | // NOTE: this field will be renamed to Codec 14 | Marshaler codec.Codec 15 | TxConfig client.TxConfig 16 | Amino *codec.LegacyAmino 17 | } 18 | -------------------------------------------------------------------------------- /docs/Tutorials/README.md: -------------------------------------------------------------------------------- 1 | # Tutorials Documentation 2 | 3 | How to use the farming module Tutorials documentation. 4 | 5 | - [Tutorials Documentation](#tutorials-documentation) 6 | - [Introduction](#introduction) 7 | - [Layout](#layout) 8 | 9 | ## Introduction 10 | 11 | This section contains Tutorials documentation for farming module. 12 | 13 | ## Layout 14 | 15 | Tutorials Documentation will come in various forms: 16 | 17 | * [Demo](./demo) - contains any type of demo or presentation for farming module 18 | * [Localnet](./localnet) - contains guideline of how to build `farmingd` and bootstrap local network -------------------------------------------------------------------------------- /cmd/farmingd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmos/cosmos-sdk/server" 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/tendermint/farming/app" 10 | _ "github.com/tendermint/farming/client/docs/statik" 11 | "github.com/tendermint/farming/cmd/farmingd/cmd" 12 | ) 13 | 14 | func main() { 15 | rootCmd, _ := cmd.NewRootCmd() 16 | 17 | if err := svrcmd.Execute(rootCmd, app.DefaultNodeHome); err != nil { 18 | switch e := err.(type) { 19 | case server.ErrorCode: 20 | os.Exit(e.Code) 21 | 22 | default: 23 | os.Exit(1) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contrib/githooks/README.md: -------------------------------------------------------------------------------- 1 | # Git hooks 2 | 3 | Installation: 4 | 5 | ``` 6 | git config core.hooksPath contrib/githooks 7 | ``` 8 | 9 | ## pre-commit 10 | 11 | The hook automatically runs `gofmt`, `goimports`, and `misspell` 12 | to correctly format the `.go` files included in the commit, provided 13 | that all the aforementioned commands are installed and available 14 | in the user's search `$PATH` environment variable: 15 | 16 | ``` 17 | go get golang.org/x/tools/cmd/goimports 18 | go get github.com/golangci/misspell/cmd/misspell@master 19 | ``` 20 | 21 | It also runs `go mod tidy` and `golangci-lint` if available. 22 | -------------------------------------------------------------------------------- /docs/How-To/README.md: -------------------------------------------------------------------------------- 1 | # How-To Documentation 2 | 3 | How to use the farming module How-To documentation. 4 | 5 | - [How-To Documentation](#how-to-documentation) 6 | - [Introduction](#introduction) 7 | - [Layout](#layout) 8 | 9 | ## Introduction 10 | 11 | This section contains how-to guides for different aspects of the farming module. 12 | 13 | ## Layout 14 | 15 | How-To Documentation will come in various forms: 16 | 17 | * [gRPC-gateway REST Routes](./api) - all the available REST APIs in the farming module. 18 | * [Command-line Interfaces](./cli) - all the available command-line interfaces in the farming module. 19 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | "github.com/cosmos/cosmos-sdk/std" 7 | 8 | "github.com/tendermint/farming/app/params" 9 | ) 10 | 11 | // MakeEncodingConfig creates an EncodingConfig for testing 12 | func MakeEncodingConfig() params.EncodingConfig { 13 | encodingConfig := params.MakeTestEncodingConfig() 14 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 15 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 16 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 17 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 18 | return encodingConfig 19 | } 20 | -------------------------------------------------------------------------------- /app/params/weights.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | const ( 4 | // farming module simulation operation weights for messages 5 | DefaultWeightMsgCreateFixedAmountPlan int = 10 6 | DefaultWeightMsgCreateRatioPlan int = 10 7 | DefaultWeightMsgStake int = 85 8 | DefaultWeightMsgUnstake int = 30 9 | DefaultWeightMsgHarvest int = 30 10 | DefaultWeightMsgRemovePlan int = 10 11 | 12 | DefaultWeightAddPublicPlanProposal int = 5 13 | DefaultWeightUpdatePublicPlanProposal int = 5 14 | DefaultWeightDeletePublicPlanProposal int = 5 15 | DefaultWeightAdvanceEpoch int = 10 16 | ) 17 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/block.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "tendermint/types/types.proto"; 8 | import "tendermint/types/evidence.proto"; 9 | 10 | message Block { 11 | Header header = 1 [(gogoproto.nullable) = false]; 12 | Data data = 2 [(gogoproto.nullable) = false]; 13 | tendermint.types.EvidenceList evidence = 3 [(gogoproto.nullable) = false]; 14 | Commit last_commit = 4; 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | .DS_Store 3 | *.swp 4 | *.swo 5 | *.swl 6 | *.swm 7 | *.swn 8 | .vscode 9 | .idea 10 | *.pyc 11 | 12 | # Build 13 | vendor 14 | build 15 | tools/bin/* 16 | examples/build/* 17 | docs/_build 18 | docs/tutorial 19 | docs/node_modules 20 | docs/modules 21 | dist 22 | tools-stamp 23 | proto-tools-stamp 24 | buf-stamp 25 | 26 | # Data - ideally these don't exist 27 | app/data/* 28 | x/farming/client/lcd/keys/* 29 | mytestnet 30 | 31 | # Testing 32 | coverage.txt 33 | profile.out 34 | sim_log_file 35 | 36 | # Vagrant 37 | .vagrant/ 38 | *.box 39 | *.log 40 | vagrant 41 | 42 | # IDE 43 | .idea/ 44 | *.iml 45 | 46 | # Localnet 47 | data/ 48 | -------------------------------------------------------------------------------- /app/params/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package params defines the simulation parameters in the simapp. 3 | 4 | It contains the default weights used for each transaction used on the module's 5 | simulation. These weights define the chance for a transaction to be simulated at 6 | any gived operation. 7 | 8 | You can repace the default values for the weights by providing a params.json 9 | file with the weights defined for each of the transaction operations: 10 | 11 | { 12 | "op_weight_msg_send": 60, 13 | "op_weight_msg_delegate": 100, 14 | } 15 | 16 | In the example above, the `MsgSend` has 60% chance to be simulated, while the 17 | `MsgDelegate` will always be simulated. 18 | */ 19 | package params 20 | -------------------------------------------------------------------------------- /x/farming/client/rest/rest.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" 8 | ) 9 | 10 | // ProposalRESTHandler returns a ProposalRESTHandler that exposes the farming plan proposal (add/update/delete) REST handler with a given sub-route. 11 | func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler { 12 | return govrest.ProposalRESTHandler{ 13 | SubRoute: "farming_plan", 14 | Handler: postProposalHandlerFn(clientCtx), 15 | } 16 | } 17 | 18 | func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc { 19 | return func(w http.ResponseWriter, r *http.Request) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | 3 | build: 4 | roots: 5 | - proto 6 | - third_party/proto 7 | excludes: 8 | - third_party/proto/google/protobuf 9 | lint: 10 | use: 11 | - DEFAULT 12 | - COMMENTS 13 | - FILE_LOWER_SNAKE_CASE 14 | except: 15 | - UNARY_RPC 16 | - COMMENT_FIELD 17 | - SERVICE_SUFFIX 18 | - PACKAGE_VERSION_SUFFIX 19 | - RPC_REQUEST_STANDARD_NAME 20 | ignore: 21 | - tendermint 22 | - gogoproto 23 | - cosmos_proto 24 | - google 25 | - confio 26 | - protoc-gen-openapiv2 27 | breaking: 28 | use: 29 | - FILE 30 | ignore: 31 | - tendermint 32 | - gogoproto 33 | - cosmos_proto 34 | - google 35 | - confio 36 | - protoc-gen-openapiv2 37 | -------------------------------------------------------------------------------- /x/farming/spec/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # `farming` 4 | 5 | ## Abstract 6 | 7 | This document specifies the farming module of the Cosmos SDK that serves farming feature. The farming module implements farming functionality that provides farming rewards to participants called farmers. 8 | 9 | A primary use case of the farming module is to provide incentives for liquidity pool investors for their pool participation. 10 | 11 | ## Contents 12 | 13 | 1. **[Concepts](01_concepts.md)** 14 | 2. **[State](02_state.md)** 15 | 3. **[State Transitions](03_state_transitions.md)** 16 | 4. **[Messages](04_messages.md)** 17 | 5. **[End-Block](05_end_block.md)** 18 | 6. **[Events](06_events.md)** 19 | 7. **[Parameters](07_params.md)** 20 | 8. **[Proposal](08_proposal.md)** 21 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/version/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.version; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | # Lint runs golangci-lint over the entire cosmos-sdk repository 3 | # This workflow is run on every pull request and push to main 4 | # The `golangci` will pass without running if no *.{go, mod, sum} files have been changed. 5 | on: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | jobs: 11 | golangci: 12 | name: golangci-lint 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 6 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: golangci/golangci-lint-action@master 18 | with: 19 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 20 | version: v1.40 21 | args: --timeout 10m 22 | github-token: ${{ secrets.github_token }} -------------------------------------------------------------------------------- /app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | "encoding/json" 7 | 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | ) 10 | 11 | // The genesis state of the blockchain is represented here as a map of raw json 12 | // messages key'd by a identifier string. 13 | // The identifier is used to determine which module genesis information belongs 14 | // to so it may be appropriately routed during init chain. 15 | // Within this application default genesis information is retrieved from 16 | // the ModuleBasicManager which populates json from each BasicModule 17 | // object provided to it during init. 18 | type GenesisState map[string]json.RawMessage 19 | 20 | // NewDefaultGenesisState generates the default state for the application. 21 | func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { 22 | return ModuleBasics.DefaultGenesis(cdc) 23 | } 24 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/validator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome contributions to our codebase and documentation. 4 | 5 | Before you create a PR on the farming module, make sure that you read and comply with this document. See the [open issues](https://github.com/tendermint/farming/issues) that we need help with. 6 | 7 | Feel free to inform us if you are willing to work on an issue. Note that the `main` branch contains the latest development version. 8 | 9 | We follow standard GitHub best practices: 10 | 11 | - Fork the repository 12 | - Make sure your `branch` is from the tip of `main` branch 13 | - Make your commits to resolve an issue 14 | - Submit a pull request to `main` 15 | 16 | General guidance: 17 | 18 | - Make sure you run tests by running `make test-all` locally to see there is any issue 19 | - Review all the checks on your PR and review the code coverage report 20 | 21 | Thank you for your contribution! -------------------------------------------------------------------------------- /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/types" 9 | "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" 10 | ) 11 | 12 | // MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. 13 | // This function should be used only internally (in the SDK). 14 | // App user shouldn't create new codecs - use the app.AppCodec instead. 15 | // [DEPRECATED] 16 | func MakeTestEncodingConfig() EncodingConfig { 17 | cdc := codec.NewLegacyAmino() 18 | interfaceRegistry := types.NewInterfaceRegistry() 19 | marshaler := codec.NewAminoCodec(cdc) 20 | 21 | return EncodingConfig{ 22 | InterfaceRegistry: interfaceRegistry, 23 | Marshaler: marshaler, 24 | TxConfig: legacytx.StdTxConfig{Cdc: cdc}, 25 | Amino: cdc, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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/types" 9 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 10 | ) 11 | 12 | // MakeTestEncodingConfig creates an EncodingConfig for a non-amino based test configuration. 13 | // This function should be used only internally (in the SDK). 14 | // App user shouldn't create new codecs - use the app.AppCodec instead. 15 | // [DEPRECATED] 16 | func MakeTestEncodingConfig() EncodingConfig { 17 | cdc := codec.NewLegacyAmino() 18 | interfaceRegistry := types.NewInterfaceRegistry() 19 | marshaler := codec.NewProtoCodec(interfaceRegistry) 20 | 21 | return EncodingConfig{ 22 | InterfaceRegistry: interfaceRegistry, 23 | Marshaler: marshaler, 24 | TxConfig: tx.NewTxConfig(marshaler, tx.DefaultSignModes), 25 | Amino: cdc, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us squash bugs! 4 | 5 | --- 6 | 7 | 12 | 13 | ## Summary of Bug 14 | 15 | 16 | 17 | ## Version 18 | 19 | 20 | 21 | ## Steps to Reproduce 22 | 23 | 24 | 25 | ## Tasks 26 | 27 | - 28 | 29 | ## References 30 | 31 | - 32 | ____ 33 | 34 | ## For Admin Use 35 | 36 | - [ ] Not duplicate issue 37 | - [ ] Appropriate labels applied 38 | - [ ] Appropriate contributors tagged 39 | - [ ] Contributor assigned/self-assigned 40 | -------------------------------------------------------------------------------- /scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | protoc_gen_gocosmos() { 6 | if ! grep "github.com/gogo/protobuf => github.com/regen-network/protobuf" go.mod &>/dev/null ; then 7 | echo -e "\tPlease run this command from somewhere inside the cosmos-sdk folder." 8 | return 1 9 | fi 10 | 11 | go get github.com/regen-network/cosmos-proto/protoc-gen-gocosmos@latest 2>/dev/null 12 | } 13 | 14 | protoc_gen_gocosmos 15 | 16 | proto_dirs=$(find ./proto -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 17 | for dir in $proto_dirs; do 18 | buf protoc \ 19 | -I "proto" \ 20 | -I "third_party/proto" \ 21 | --gocosmos_out=plugins=interfacetype+grpc,\ 22 | Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types:. \ 23 | --grpc-gateway_out=logtostderr=true,allow_colon_final_segments=true:. \ 24 | $(find "${dir}" -maxdepth 1 -name '*.proto') 25 | 26 | done 27 | 28 | # move proto files to the right places 29 | cp -r github.com/tendermint/farming/* ./ 30 | rm -rf github.com -------------------------------------------------------------------------------- /contrib/devtools/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bufbuild/buf:latest as BUILDER 2 | 3 | FROM golang:alpine 4 | 5 | ENV GOLANG_PROTOBUF_VERSION=1.3.5 \ 6 | GOGO_PROTOBUF_VERSION=1.3.2 \ 7 | GRPC_GATEWAY_VERSION=1.14.7 8 | 9 | RUN GO111MODULE=on go get \ 10 | github.com/golang/protobuf/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} \ 11 | github.com/gogo/protobuf/protoc-gen-gogo@v${GOGO_PROTOBUF_VERSION} \ 12 | github.com/gogo/protobuf/protoc-gen-gogofast@v${GOGO_PROTOBUF_VERSION} \ 13 | github.com/gogo/protobuf/protoc-gen-gogofaster@v${GOGO_PROTOBUF_VERSION} \ 14 | github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} \ 15 | github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} \ 16 | github.com/regen-network/cosmos-proto/protoc-gen-gocosmos@latest 17 | 18 | RUN GO111MODULE=on go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc 19 | 20 | RUN apk add --no-cache \ 21 | nodejs \ 22 | npm 23 | 24 | RUN npm install -g swagger-combine 25 | 26 | COPY --from=BUILDER /usr/local/bin /usr/local/bin 27 | -------------------------------------------------------------------------------- /contrib/githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | CMDS='git go gofmt goimports misspell' 6 | STAGED_GO_FILES=$(git diff --cached --name-only -- '*.go') 7 | 8 | f_echo_stderr() { 9 | echo $@ >&2 10 | } 11 | 12 | f_exit_success() { 13 | [ x"$@" != "x" ] && f_echo_stderr $@ || exit 0 14 | } 15 | trap f_exit_success EXIT 16 | 17 | f_check_cmds() { 18 | for cmd in ${CMDS}; do 19 | which ${cmd} &>/dev/null || f_exit_success "couldn't find ${cmd}, skipping" 20 | done 21 | } 22 | 23 | f_check_cmds 24 | 25 | if [[ $STAGED_GO_FILES != "" ]]; then 26 | f_echo_stderr "[pre-commit] fmt'ing staged files..." 27 | for file in $STAGED_GO_FILES; do 28 | if [[ $file =~ vendor/ ]] || [[ $file =~ client/docs/statik/ ]] || [[ $file =~ tests/mocks/ ]] || [[ $file =~ \.pb\.go ]]; then 29 | continue 30 | fi 31 | 32 | gofmt -w -s $file 33 | misspell -w $file 34 | goimports -w -local github.com/cosmos/cosmos-sdk $file 35 | git add $file 36 | 37 | done 38 | fi 39 | 40 | # Run go mod tidy 41 | go mod tidy && git add go.mod go.sum 42 | -------------------------------------------------------------------------------- /scripts/protoc-swagger-gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | mkdir -p ./tmp-swagger-gen 6 | proto_dirs=$(find ./proto -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 7 | for dir in $proto_dirs; do 8 | 9 | # generate swagger files (filter query files) 10 | query_file=$(find "${dir}" -maxdepth 1 \( -name 'query.proto' -o -name 'msg.proto' \)) 11 | if [[ ! -z "$query_file" ]]; then 12 | buf protoc \ 13 | -I "proto" \ 14 | -I "third_party/proto" \ 15 | "$query_file" \ 16 | --swagger_out=./tmp-swagger-gen \ 17 | --swagger_opt=logtostderr=true --swagger_opt=fqn_for_swagger_name=true --swagger_opt=simple_operation_ids=true 18 | fi 19 | done 20 | 21 | # combine swagger files 22 | # uses nodejs package `swagger-combine`. 23 | # all the individual swagger files need to be configured in `config.json` for merging 24 | swagger-combine ./client/docs/config.json -o ./client/docs/swagger-ui/swagger.yaml -f yaml --continueOnConflictingPaths true --includeDefinitions true 25 | 26 | # clean swagger files 27 | rm -rf ./tmp-swagger-gen -------------------------------------------------------------------------------- /third_party/proto/tendermint/crypto/proof.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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/protoc-gen-openapiv2/options/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 3 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 4 | 5 | package(default_visibility = ["//visibility:public"]) 6 | 7 | filegroup( 8 | name = "options_proto_files", 9 | srcs = [ 10 | "annotations.proto", 11 | "openapiv2.proto", 12 | ], 13 | ) 14 | 15 | go_library( 16 | name = "go_default_library", 17 | embed = [":options_go_proto"], 18 | importpath = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options", 19 | ) 20 | 21 | proto_library( 22 | name = "options_proto", 23 | srcs = [ 24 | "annotations.proto", 25 | "openapiv2.proto", 26 | ], 27 | deps = [ 28 | "@com_google_protobuf//:descriptor_proto", 29 | "@com_google_protobuf//:struct_proto", 30 | ], 31 | ) 32 | 33 | go_proto_library( 34 | name = "options_go_proto", 35 | compilers = ["//:go_apiv2"], 36 | importpath = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options", 37 | proto = ":options_proto", 38 | ) 39 | -------------------------------------------------------------------------------- /x/farming/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 6 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 7 | ) 8 | 9 | // BankKeeper defines the expected bank send keeper 10 | type BankKeeper interface { 11 | SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error 12 | GetSupply(ctx sdk.Context, denom string) sdk.Coin 13 | GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 14 | SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 15 | SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 16 | InputOutputCoins(ctx sdk.Context, inputs []banktypes.Input, outputs []banktypes.Output) error 17 | // MintCoins is used only for simulation test codes 18 | MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error 19 | } 20 | 21 | // AccountKeeper defines the expected account keeper 22 | type AccountKeeper interface { 23 | GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI 24 | GetModuleAddress(name string) sdk.AccAddress 25 | } 26 | -------------------------------------------------------------------------------- /third_party/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 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/p2p/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.p2p; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | message NetAddress { 9 | string id = 1 [(gogoproto.customname) = "ID"]; 10 | string ip = 2 [(gogoproto.customname) = "IP"]; 11 | uint32 port = 3; 12 | } 13 | 14 | message ProtocolVersion { 15 | uint64 p2p = 1 [(gogoproto.customname) = "P2P"]; 16 | uint64 block = 2; 17 | uint64 app = 3; 18 | } 19 | 20 | message DefaultNodeInfo { 21 | ProtocolVersion protocol_version = 1 [(gogoproto.nullable) = false]; 22 | string default_node_id = 2 [(gogoproto.customname) = "DefaultNodeID"]; 23 | string listen_addr = 3; 24 | string network = 4; 25 | string version = 5; 26 | bytes channels = 6; 27 | string moniker = 7; 28 | DefaultNodeInfoOther other = 8 [(gogoproto.nullable) = false]; 29 | } 30 | 31 | message DefaultNodeInfoOther { 32 | string tx_index = 1; 33 | string rpc_address = 2 [(gogoproto.customname) = "RPCAddress"]; 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tendermint/farming 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/cosmos/cosmos-sdk v0.44.5 7 | github.com/gogo/protobuf v1.3.3 8 | github.com/golang/mock v1.6.0 9 | github.com/golang/protobuf v1.5.2 10 | github.com/gorilla/mux v1.8.0 11 | github.com/gravity-devs/liquidity v1.4.2 12 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 13 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 14 | github.com/rakyll/statik v0.1.7 15 | github.com/regen-network/cosmos-proto v0.3.1 16 | github.com/spf13/cast v1.3.1 17 | github.com/spf13/cobra v1.2.1 18 | github.com/spf13/pflag v1.0.5 19 | github.com/stretchr/testify v1.7.0 20 | github.com/tendermint/budget v1.1.1 21 | github.com/tendermint/tendermint v0.34.14 22 | github.com/tendermint/tm-db v0.6.4 23 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 24 | google.golang.org/grpc v1.42.0 25 | google.golang.org/protobuf v1.27.1 26 | gopkg.in/yaml.v2 v2.4.0 27 | ) 28 | 29 | replace ( 30 | github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 31 | github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 32 | google.golang.org/grpc => google.golang.org/grpc v1.33.2 33 | ) 34 | -------------------------------------------------------------------------------- /x/farming/simulation/params_test.go: -------------------------------------------------------------------------------- 1 | package simulation_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/tendermint/farming/x/farming/simulation" 10 | ) 11 | 12 | func TestParamChanges(t *testing.T) { 13 | s := rand.NewSource(1) 14 | r := rand.New(s) 15 | 16 | expected := []struct { 17 | composedKey string 18 | key string 19 | simValue string 20 | subspace string 21 | }{ 22 | {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "[{\"denom\":\"stake\",\"amount\":\"98498081\"}]", "farming"}, 23 | {"farming/NextEpochDays", "NextEpochDays", "7", "farming"}, 24 | {"farming/FarmingFeeCollector", "FarmingFeeCollector", "\"cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x\"", "farming"}, 25 | {"farming/MaxNumPrivatePlans", "MaxNumPrivatePlans", "4575", "farming"}, 26 | } 27 | 28 | paramChanges := simulation.ParamChanges(r) 29 | require.Len(t, paramChanges, 4) 30 | 31 | for i, p := range paramChanges { 32 | require.Equal(t, expected[i].composedKey, p.ComposedKey()) 33 | require.Equal(t, expected[i].key, p.Key()) 34 | require.Equal(t, expected[i].simValue, p.SimValue()(r)) 35 | require.Equal(t, expected[i].subspace, p.Subspace()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Create a proposal to request a feature 4 | 5 | --- 6 | 7 | 13 | 14 | ## Summary 15 | 16 | 17 | 18 | ## Problem Definition 19 | 20 | 24 | 25 | ## Proposal 26 | 27 | 28 | 29 | ## Tasks 30 | 31 | - 32 | 33 | ## References 34 | 35 | - 36 | ____ 37 | 38 | #### For Admin Use 39 | 40 | - [ ] Not duplicate issue 41 | - [ ] Appropriate labels applied 42 | - [ ] Appropriate contributors tagged 43 | - [ ] Contributor assigned/self-assigned 44 | -------------------------------------------------------------------------------- /x/farming/spec/05_end_block.md: -------------------------------------------------------------------------------- 1 | 2 | # End-Block 3 | 4 | At each end-block call, the `farming` module operations are specified to execute. 5 | 6 | ++ https://github.com/tendermint/farming/blob/69db071ce3/x/farming/abci.go#L13-L46 7 | 8 | At the end of each block: 9 | 10 | - Terminates plans if their end time has passed over the current block time. 11 | 12 | - Sends all remaining coins in the plan's farming pool account `FarmingPoolAddress` to the termination address `TerminationAddress`. 13 | - Marks the plan as terminated by making `Terminated` true. 14 | - Allocates farming rewards. 15 | - Processes `QueueStaking` to be staked. 16 | - Sets `LastEpochTime` to track in case of chain upgrade. 17 | 18 | ## Internal state CurrentEpochDays 19 | 20 | Although a global parameter `NextEpochDays` exists, the farming module uses an internal state `CurrentEpochDays` to prevent impacting rewards allocation. 21 | 22 | Suppose `NextEpochDays` is 7 and it is proposed to change the value to 1 through governance proposal. Although the proposal is passed, rewards allocation must continue to proceed with 7, not 1. 23 | 24 | To explore internal state `CurrentEpochDays` in more detail, see the [test code](https://github.com/tendermint/farming/blob/69db071ce3/x/farming/abci_test.go#L12-L64). 25 | -------------------------------------------------------------------------------- /x/farming/types/utils.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/address" 8 | "github.com/tendermint/tendermint/crypto" 9 | ) 10 | 11 | // ParseTime parses string time to time in RFC3339 format. 12 | // This is used only for internal testing purpose. 13 | func ParseTime(s string) time.Time { 14 | t, err := time.Parse(time.RFC3339, s) 15 | if err != nil { 16 | panic(err) 17 | } 18 | return t 19 | } 20 | 21 | // DeriveAddress derives an address with the given address length type, module name, and 22 | // address derivation name. It is used to derive private plan farming pool address, and staking reserve address. 23 | func DeriveAddress(addressType AddressType, moduleName, name string) sdk.AccAddress { 24 | switch addressType { 25 | case AddressType32Bytes: 26 | return sdk.AccAddress(address.Module(moduleName, []byte(name))) 27 | case AddressType20Bytes: 28 | return sdk.AccAddress(crypto.AddressHash([]byte(moduleName + name))) 29 | default: 30 | return sdk.AccAddress{} 31 | } 32 | } 33 | 34 | // DateRangeIncludes returns true if the target date included on the start, end time range. 35 | // End time is exclusive and start time is inclusive. 36 | func DateRangeIncludes(startTime, endTime, targetTime time.Time) bool { 37 | return endTime.After(targetTime) && !startTime.After(targetTime) 38 | } 39 | -------------------------------------------------------------------------------- /x/farming/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // Event types for the farming module. 4 | const ( 5 | EventTypeCreateFixedAmountPlan = "create_fixed_amount_plan" 6 | EventTypeCreateRatioPlan = "create_ratio_plan" 7 | EventTypeStake = "stake" 8 | EventTypeUnstake = "unstake" 9 | EventTypeHarvest = "harvest" 10 | EventTypeRemovePlan = "remove_plan" 11 | EventTypeRewardsWithdrawn = "rewards_withdrawn" 12 | EventTypePlanTerminated = "plan_terminated" 13 | EventTypeRewardsAllocated = "rewards_allocated" 14 | 15 | AttributeKeyPlanId = "plan_id" //nolint:golint 16 | AttributeKeyPlanName = "plan_name" 17 | AttributeKeyFarmingPoolAddress = "farming_pool_address" 18 | AttributeKeyTerminationAddress = "termination_address" 19 | AttributeKeyStakingCoins = "staking_coins" 20 | AttributeKeyUnstakingCoins = "unstaking_coins" 21 | AttributeKeyRewardCoins = "reward_coins" 22 | AttributeKeyStartTime = "start_time" 23 | AttributeKeyEndTime = "end_time" 24 | AttributeKeyEpochAmount = "epoch_amount" 25 | AttributeKeyEpochRatio = "epoch_ratio" 26 | AttributeKeyFarmer = "farmer" 27 | AttributeKeyAmount = "amount" 28 | AttributeKeyStakingCoinDenom = "staking_coin_denom" 29 | AttributeKeyStakingCoinDenoms = "staking_coin_denoms" 30 | ) 31 | -------------------------------------------------------------------------------- /x/farming/simulation/params.go: -------------------------------------------------------------------------------- 1 | package simulation 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | 9 | simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 10 | "github.com/cosmos/cosmos-sdk/x/simulation" 11 | 12 | "github.com/tendermint/farming/x/farming/types" 13 | ) 14 | 15 | // ParamChanges defines the parameters that can be modified by param change proposals 16 | // on the simulation. 17 | func ParamChanges(r *rand.Rand) []simtypes.ParamChange { 18 | return []simtypes.ParamChange{ 19 | simulation.NewSimParamChange(types.ModuleName, string(types.KeyPrivatePlanCreationFee), 20 | func(r *rand.Rand) string { 21 | bz, err := GenPrivatePlanCreationFee(r).MarshalJSON() 22 | if err != nil { 23 | panic(err) 24 | } 25 | return string(bz) 26 | }, 27 | ), 28 | simulation.NewSimParamChange(types.ModuleName, string(types.KeyNextEpochDays), 29 | func(r *rand.Rand) string { 30 | return fmt.Sprintf("%d", GenNextEpochDays(r)) 31 | }, 32 | ), 33 | simulation.NewSimParamChange(types.ModuleName, string(types.KeyFarmingFeeCollector), 34 | func(r *rand.Rand) string { 35 | return fmt.Sprintf("\"%s\"", GenFarmingFeeCollector(r)) 36 | }, 37 | ), 38 | simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxNumPrivatePlans), 39 | func(r *rand.Rand) string { 40 | return fmt.Sprintf("%d", GenMaxNumPrivatePlans(r)) 41 | }, 42 | ), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/Reference/README.md: -------------------------------------------------------------------------------- 1 | # Reference Documentation 2 | 3 | How to use the farming module Reference documentation. 4 | 5 | - [Reference Documentation](#reference-documentation) 6 | - [Introduction](#introduction) 7 | - [Contributing](#contributing) 8 | - [Layout](#layout) 9 | - [Reference](#reference) 10 | 11 | ## Introduction 12 | 13 | This section contains **Reference documentation** for farming module. [Reference Documentation](https://documentation.divio.com/reference/) is intended to be **information-oriented**. Content must allow the reader to easily navigate the content and use the content in conjunction with the code. 14 | 15 | ## Contributing 16 | 17 | * The content must be dry, clear, and terse in style. 18 | * All documentation should be written following [Google Documentation Best Practice](https://google.github.io/styleguide/docguide/best_practices.html) 19 | * Generate as much documentation as possible from the code. 20 | * Raise a PR for all documentation changes 21 | 22 | ## Layout 23 | 24 | Reference Documentation will come in various forms: 25 | 26 | * [A record of governance proposals](./proposals) - all signal governance proposals reside related to farming module. 27 | 28 | ## Reference 29 | 30 | - [Google Style Guide for Markdown](https://github.com/google/styleguide/blob/gh-pages/docguide/style.md) 31 | - [Write the Docs global community](https://www.writethedocs.org/) 32 | - [Write the Docs Code of Conduct](https://www.writethedocs.org/code-of-conduct/#the-principles) -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Description 8 | 9 | 12 | 13 | closes: #XXXX 14 | 15 | ## Tasks 16 | 17 | - [ ] 18 | 19 | ## References 20 | 21 | - 22 | 23 | --- 24 | 25 | Before we can merge this PR, please make sure that all the following items have been 26 | checked off. If any of the checklist items are not applicable, please leave them but 27 | write a little note why. 28 | 29 | - [ ] Appropriate labels applied 30 | - [ ] Targeted PR against correct branch 31 | - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. 32 | - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). 33 | - [ ] Wrote unit and integration 34 | - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) 35 | - [ ] Added relevant `godoc` [comments](https://go.dev/blog/godoc). 36 | - [ ] Re-reviewed `Files changed` in the Github PR explorer 37 | - [ ] Review `Codecov Report` in the comment section below once CI passes 38 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | How to use the farming module documentation. 4 | 5 | * [Documentation](#documentation) 6 | * [Overview](#overview) 7 | * [Contributing](#contributing) 8 | * [Reference](#reference) 9 | 10 | ## Overview 11 | 12 | In farming module, we use the *Grand Unified Theory of Documentation* (David Laing) as described by [Divio](https://documentation.divio.com/) as a basis for our documentation strategy. 13 | 14 | This approach outlines four specific use cases for documentation: 15 | 16 | * [Tutorials](./Tutorials/README.md) 17 | * [How-Tos](./How-To/README.md) 18 | * [Explanation](./Explanation/README.md) 19 | * [Reference](./Reference/README.md) 20 | 21 | For further background please see [the ADR relating to the documentation structure](./Explanation/ADR/adr-002-docs-structure.md). 22 | 23 | ## Contributing 24 | 25 | * Write all documentation following [Google Documentation Best Practice](https://google.github.io/styleguide/docguide/best_practices.html) 26 | * Generate as much documentation as possible from the code. 27 | * Raise a PR for all documentation changes 28 | * Follow our [Code of Conduct](../CONTRIBUTING.md) 29 | 30 | ## Reference 31 | 32 | - [Google Style Guide for Markdown](https://github.com/google/styleguide/blob/gh-pages/docguide/style.md) 33 | - [Write the Docs global community](https://www.writethedocs.org/) 34 | - [Write the Docs Code of Conduct](https://www.writethedocs.org/code-of-conduct/#the-principles) 35 | - [The good docs project](https://github.com/thegooddocsproject) 36 | - [Readme editor](https://readme.so/editor) -------------------------------------------------------------------------------- /client/docs/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/evidence.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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 | -------------------------------------------------------------------------------- /x/farming/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 5 | ) 6 | 7 | // farming module sentinel errors 8 | var ( 9 | ErrInvalidPlanType = sdkerrors.Register(ModuleName, 2, "invalid plan type") 10 | ErrInvalidPlanName = sdkerrors.Register(ModuleName, 3, "invalid plan name") 11 | ErrInvalidPlanEndTime = sdkerrors.Register(ModuleName, 4, "invalid plan end time") 12 | ErrInvalidStakingCoinWeights = sdkerrors.Register(ModuleName, 5, "invalid staking coin weights") 13 | ErrInvalidTotalEpochRatio = sdkerrors.Register(ModuleName, 6, "invalid total epoch ratio") 14 | ErrStakingNotExists = sdkerrors.Register(ModuleName, 7, "staking not exists") 15 | ErrConflictPrivatePlanFarmingPool = sdkerrors.Register(ModuleName, 8, "the address is already in use, please use a different plan name") 16 | ErrInvalidStakingReservedAmount = sdkerrors.Register(ModuleName, 9, "staking reserved amount invariant broken") 17 | ErrInvalidRemainingRewardsAmount = sdkerrors.Register(ModuleName, 10, "remaining rewards amount invariant broken") 18 | ErrInvalidOutstandingRewardsAmount = sdkerrors.Register(ModuleName, 11, "outstanding rewards amount invariant broken") 19 | ErrNumPrivatePlansLimit = sdkerrors.Register(ModuleName, 12, "cannot create private plans more than the limit") 20 | ErrNumMaxDenomsLimit = sdkerrors.Register(ModuleName, 13, "number of denoms cannot exceed the limit") 21 | ErrInvalidEpochAmount = sdkerrors.Register(ModuleName, 14, "invalid epoch amount") 22 | ErrRatioPlanDisabled = sdkerrors.Register(ModuleName, 15, "creation of ratio plans is disabled") 23 | ) 24 | -------------------------------------------------------------------------------- /x/farming/simulation/decoder.go: -------------------------------------------------------------------------------- 1 | package simulation 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | "github.com/cosmos/cosmos-sdk/types/kv" 9 | 10 | "github.com/tendermint/farming/x/farming/types" 11 | ) 12 | 13 | // NewDecodeStore returns a decoder function closure that unmarshals the KVPair's 14 | // Value to the corresponding farming type. 15 | func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { 16 | return func(kvA, kvB kv.Pair) string { 17 | switch { 18 | case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix): 19 | var pA, pB types.BasePlan 20 | cdc.MustUnmarshal(kvA.Value, &pA) 21 | cdc.MustUnmarshal(kvB.Value, &pB) 22 | return fmt.Sprintf("%v\n%v", pA, pB) 23 | 24 | case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix): 25 | var sA, sB types.Staking 26 | cdc.MustUnmarshal(kvA.Value, &sA) 27 | cdc.MustUnmarshal(kvB.Value, &sB) 28 | return fmt.Sprintf("%v\n%v", sA, sB) 29 | 30 | case bytes.Equal(kvA.Key[:1], types.QueuedStakingKeyPrefix): 31 | var sA, sB types.QueuedStaking 32 | cdc.MustUnmarshal(kvA.Value, &sA) 33 | cdc.MustUnmarshal(kvB.Value, &sB) 34 | return fmt.Sprintf("%v\n%v", sA, sB) 35 | 36 | case bytes.Equal(kvA.Key[:1], types.HistoricalRewardsKeyPrefix): 37 | var rA, rB types.HistoricalRewards 38 | cdc.MustUnmarshal(kvA.Value, &rA) 39 | cdc.MustUnmarshal(kvB.Value, &rB) 40 | return fmt.Sprintf("%v\n%v", rA, rB) 41 | 42 | case bytes.Equal(kvA.Key[:1], types.OutstandingRewardsKeyPrefix): 43 | var rA, rB types.OutstandingRewards 44 | cdc.MustUnmarshal(kvA.Value, &rA) 45 | cdc.MustUnmarshal(kvB.Value, &rB) 46 | return fmt.Sprintf("%v\n%v", rA, rB) 47 | 48 | default: 49 | panic(fmt.Sprintf("invalid farming key prefix %X", kvA.Key[:1])) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /x/farming/client/cli/flags.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | flag "github.com/spf13/pflag" 7 | ) 8 | 9 | const ( 10 | FlagPlanType = "plan-type" 11 | FlagFarmingPoolAddr = "farming-pool-addr" 12 | FlagTerminationAddr = "termination-addr" 13 | FlagStakingCoinDenom = "staking-coin-denom" 14 | FlagTerminated = "terminated" 15 | FlagAll = "all" 16 | ) 17 | 18 | // flagSetPlans returns the FlagSet used for farming plan related opertations. 19 | func flagSetPlans() *flag.FlagSet { 20 | fs := flag.NewFlagSet("", flag.ContinueOnError) 21 | 22 | fs.String(FlagPlanType, "", "The plan type; private or public") 23 | fs.String(FlagFarmingPoolAddr, "", "The bech32 address of the farming pool account") 24 | fs.String(FlagTerminationAddr, "", "The bech32 address of the termination account") 25 | fs.String(FlagStakingCoinDenom, "", "The staking coin denom") 26 | fs.String(FlagTerminated, "", "Whether the plan is terminated or not (true/false)") 27 | 28 | return fs 29 | } 30 | 31 | // flagSetStakings returns the FlagSet used for farmer's staking coin denom. 32 | func flagSetStakings() *flag.FlagSet { 33 | fs := flag.NewFlagSet("", flag.ContinueOnError) 34 | 35 | fs.String(FlagStakingCoinDenom, "", "The staking coin denom") 36 | 37 | return fs 38 | } 39 | 40 | // flagSetRewards returns the FlagSet used for farmer's rewards. 41 | func flagSetRewards() *flag.FlagSet { 42 | fs := flag.NewFlagSet("", flag.ContinueOnError) 43 | 44 | fs.String(FlagStakingCoinDenom, "", "The staking coin denom") 45 | 46 | return fs 47 | } 48 | 49 | // flagSetHarvest returns the FlagSet used for harvest all staking coin denoms. 50 | func flagSetHarvest() *flag.FlagSet { 51 | fs := flag.NewFlagSet("", flag.ContinueOnError) 52 | 53 | fs.Bool(FlagAll, false, "Harvest for all staking coin denoms") 54 | 55 | return fs 56 | } 57 | -------------------------------------------------------------------------------- /x/farming/abci.go: -------------------------------------------------------------------------------- 1 | package farming 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/cosmos/cosmos-sdk/telemetry" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | "github.com/tendermint/farming/x/farming/keeper" 10 | "github.com/tendermint/farming/x/farming/types" 11 | ) 12 | 13 | func EndBlocker(ctx sdk.Context, k keeper.Keeper) { 14 | defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) 15 | 16 | logger := k.Logger(ctx) 17 | 18 | k.PruneTotalStakings(ctx) 19 | 20 | for _, plan := range k.GetPlans(ctx) { 21 | if !plan.IsTerminated() && !ctx.BlockTime().Before(plan.GetEndTime()) { 22 | if err := k.TerminatePlan(ctx, plan); err != nil { 23 | logger.Error("failed to terminate plan", "plan_id", plan.GetId()) 24 | } 25 | } 26 | } 27 | 28 | // CurrentEpochDays is initialized with the value of NextEpochDays in genesis, and 29 | // it is used here to prevent from affecting the epoch days for farming rewards allocation. 30 | // Suppose NextEpochDays is 7 days, and it is proposed to change the value to 1 day through governance proposal. 31 | // Although the proposal is passed, farming rewards allocation should continue to proceed with 7 days, 32 | // and then it gets updated. 33 | currentEpochDays := k.GetCurrentEpochDays(ctx) 34 | 35 | lastEpochTime, found := k.GetLastEpochTime(ctx) 36 | if !found { 37 | k.SetLastEpochTime(ctx, ctx.BlockTime()) 38 | } else { 39 | y, m, d := lastEpochTime.AddDate(0, 0, int(currentEpochDays)).Date() 40 | y2, m2, d2 := ctx.BlockTime().Date() 41 | if !time.Date(y2, m2, d2, 0, 0, 0, 0, time.UTC).Before(time.Date(y, m, d, 0, 0, 0, 0, time.UTC)) { 42 | if err := k.AdvanceEpoch(ctx); err != nil { 43 | panic(err) 44 | } 45 | if params := k.GetParams(ctx); params.NextEpochDays != currentEpochDays { 46 | k.SetCurrentEpochDays(ctx, params.NextEpochDays) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /third_party/proto/protoc-gen-openapiv2/options/annotations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.gateway.protoc_gen_openapiv2.options; 4 | 5 | option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"; 6 | 7 | import "google/protobuf/descriptor.proto"; 8 | import "protoc-gen-openapiv2/options/openapiv2.proto"; 9 | 10 | extend google.protobuf.FileOptions { 11 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 12 | // 13 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 14 | // different descriptor messages. 15 | Swagger openapiv2_swagger = 1042; 16 | } 17 | extend google.protobuf.MethodOptions { 18 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 19 | // 20 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 21 | // different descriptor messages. 22 | Operation openapiv2_operation = 1042; 23 | } 24 | extend google.protobuf.MessageOptions { 25 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 26 | // 27 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 28 | // different descriptor messages. 29 | Schema openapiv2_schema = 1042; 30 | } 31 | extend google.protobuf.ServiceOptions { 32 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 33 | // 34 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 35 | // different descriptor messages. 36 | Tag openapiv2_tag = 1042; 37 | } 38 | extend google.protobuf.FieldOptions { 39 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 40 | // 41 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 42 | // different descriptor messages. 43 | JSONSchema openapiv2_field = 1042; 44 | } 45 | -------------------------------------------------------------------------------- /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 35 | // order. 36 | bool reverse = 5; 37 | } 38 | 39 | // PageResponse is to be embedded in gRPC response messages where the 40 | // corresponding request message has used PageRequest. 41 | // 42 | // message SomeResponse { 43 | // repeated Bar results = 1; 44 | // PageResponse page = 2; 45 | // } 46 | message PageResponse { 47 | // next_key is the key to be passed to PageRequest.key to 48 | // query the next page most efficiently 49 | bytes next_key = 1; 50 | 51 | // total is total number of results available if PageRequest.count_total 52 | // was set, its value is undefined otherwise 53 | uint64 total = 2; 54 | } 55 | -------------------------------------------------------------------------------- /x/farming/abci_test.go: -------------------------------------------------------------------------------- 1 | package farming_test 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tendermint/farming/x/farming" 7 | "github.com/tendermint/farming/x/farming/types" 8 | 9 | _ "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | func (suite *ModuleTestSuite) TestEndBlockerEpochDaysTest() { 13 | epochDaysTest := func(formerEpochDays, targetNextEpochDays uint32) { 14 | suite.SetupTest() 15 | 16 | params := suite.keeper.GetParams(suite.ctx) 17 | params.NextEpochDays = formerEpochDays 18 | suite.keeper.SetParams(suite.ctx, params) 19 | suite.keeper.SetCurrentEpochDays(suite.ctx, formerEpochDays) 20 | 21 | t := types.ParseTime("2021-08-01T00:00:00Z") 22 | suite.ctx = suite.ctx.WithBlockTime(t) 23 | farming.EndBlocker(suite.ctx, suite.keeper) 24 | 25 | lastEpochTime, _ := suite.keeper.GetLastEpochTime(suite.ctx) 26 | 27 | for i := 1; i < 200; i++ { 28 | t = t.Add(1 * time.Hour) 29 | suite.ctx = suite.ctx.WithBlockTime(t) 30 | farming.EndBlocker(suite.ctx, suite.keeper) 31 | 32 | if i == 10 { // 10 hours passed 33 | params := suite.keeper.GetParams(suite.ctx) 34 | params.NextEpochDays = targetNextEpochDays 35 | suite.keeper.SetParams(suite.ctx, params) 36 | } 37 | 38 | currentEpochDays := suite.keeper.GetCurrentEpochDays(suite.ctx) 39 | t2, _ := suite.keeper.GetLastEpochTime(suite.ctx) 40 | 41 | if uint32(i) == formerEpochDays*24 { 42 | suite.Require().True(t2.After(lastEpochTime)) 43 | suite.Require().Equal(t2.Sub(lastEpochTime).Hours(), float64(formerEpochDays*24)) 44 | suite.Require().Equal(targetNextEpochDays, currentEpochDays) 45 | } 46 | 47 | if uint32(i) == formerEpochDays*24+targetNextEpochDays*24 { 48 | suite.Require().Equal(t2.Sub(lastEpochTime).Hours(), float64(currentEpochDays*24)) 49 | suite.Require().Equal(targetNextEpochDays, currentEpochDays) 50 | } 51 | 52 | lastEpochTime = t2 53 | } 54 | } 55 | 56 | // increasing case 57 | epochDaysTest(1, 7) 58 | 59 | // decreasing case 60 | epochDaysTest(7, 1) 61 | 62 | // stay case 63 | epochDaysTest(1, 1) 64 | } 65 | -------------------------------------------------------------------------------- /x/farming/simulation/decoder_test.go: -------------------------------------------------------------------------------- 1 | package simulation_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/cosmos/cosmos-sdk/simapp" 10 | "github.com/cosmos/cosmos-sdk/types/kv" 11 | 12 | "github.com/tendermint/farming/x/farming/simulation" 13 | "github.com/tendermint/farming/x/farming/types" 14 | ) 15 | 16 | func TestDecodeFarmingStore(t *testing.T) { 17 | cdc := simapp.MakeTestEncodingConfig().Marshaler 18 | dec := simulation.NewDecodeStore(cdc) 19 | 20 | basePlan := types.BasePlan{} 21 | staking := types.Staking{} 22 | queuedStaking := types.QueuedStaking{} 23 | historicalRewards := types.HistoricalRewards{} 24 | outstandingRewards := types.OutstandingRewards{} 25 | 26 | kvPairs := kv.Pairs{ 27 | Pairs: []kv.Pair{ 28 | {Key: types.PlanKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, 29 | {Key: types.StakingKeyPrefix, Value: cdc.MustMarshal(&staking)}, 30 | {Key: types.QueuedStakingKeyPrefix, Value: cdc.MustMarshal(&queuedStaking)}, 31 | {Key: types.HistoricalRewardsKeyPrefix, Value: cdc.MustMarshal(&historicalRewards)}, 32 | {Key: types.OutstandingRewardsKeyPrefix, Value: cdc.MustMarshal(&outstandingRewards)}, 33 | {Key: []byte{0x99}, Value: []byte{0x99}}, 34 | }, 35 | } 36 | 37 | tests := []struct { 38 | name string 39 | expectedLog string 40 | }{ 41 | {"Plan", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, 42 | {"Staking", fmt.Sprintf("%v\n%v", staking, staking)}, 43 | {"QueuedStaking", fmt.Sprintf("%v\n%v", queuedStaking, queuedStaking)}, 44 | {"HistoricalRewardsKeyPrefix", fmt.Sprintf("%v\n%v", historicalRewards, historicalRewards)}, 45 | {"OutstandingRewardsKeyPrefix", fmt.Sprintf("%v\n%v", outstandingRewards, outstandingRewards)}, 46 | {"other", ""}, 47 | } 48 | for i, tt := range tests { 49 | i, tt := i, tt 50 | t.Run(tt.name, func(t *testing.T) { 51 | switch i { 52 | case len(tests) - 1: 53 | require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) 54 | default: 55 | require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /x/farming/types/params_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" 11 | 12 | "github.com/tendermint/farming/x/farming/types" 13 | ) 14 | 15 | func TestParams(t *testing.T) { 16 | require.IsType(t, paramstypes.KeyTable{}, types.ParamKeyTable()) 17 | 18 | defaultParams := types.DefaultParams() 19 | 20 | paramsStr := `private_plan_creation_fee: 21 | - denom: stake 22 | amount: "1000000000" 23 | next_epoch_days: 1 24 | farming_fee_collector: cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x 25 | delayed_staking_gas_fee: 60000 26 | max_num_private_plans: 10000 27 | ` 28 | require.Equal(t, paramsStr, defaultParams.String()) 29 | } 30 | 31 | func TestParamsValidate(t *testing.T) { 32 | require.NoError(t, types.DefaultParams().Validate()) 33 | 34 | testCases := []struct { 35 | name string 36 | configure func(*types.Params) 37 | expectedErr string 38 | }{ 39 | { 40 | "EmptyPrivatePlanCreationFee", 41 | func(params *types.Params) { 42 | params.PrivatePlanCreationFee = sdk.NewCoins() 43 | }, 44 | "", 45 | }, 46 | { 47 | "ZeroNextEpochDays", 48 | func(params *types.Params) { 49 | params.NextEpochDays = uint32(0) 50 | }, 51 | "next epoch days must be positive: 0", 52 | }, 53 | { 54 | "EmptyFarmingFeeCollector", 55 | func(params *types.Params) { 56 | params.FarmingFeeCollector = "" 57 | }, 58 | "farming fee collector address must not be empty", 59 | }, 60 | } 61 | 62 | for _, tc := range testCases { 63 | t.Run(tc.name, func(t *testing.T) { 64 | params := types.DefaultParams() 65 | tc.configure(¶ms) 66 | err := params.Validate() 67 | 68 | var err2 error 69 | for _, p := range params.ParamSetPairs() { 70 | err := p.ValidatorFn(reflect.ValueOf(p.Value).Elem().Interface()) 71 | if err != nil { 72 | err2 = err 73 | break 74 | } 75 | } 76 | if tc.expectedErr != "" { 77 | require.EqualError(t, err, tc.expectedErr) 78 | require.EqualError(t, err2, tc.expectedErr) 79 | } else { 80 | require.Nil(t, err) 81 | require.Nil(t, err2) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /x/farming/keeper/epoch.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "time" 5 | 6 | gogotypes "github.com/gogo/protobuf/types" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | 10 | "github.com/tendermint/farming/x/farming/types" 11 | ) 12 | 13 | // GetLastEpochTime returns the last time the epoch ended. 14 | func (k Keeper) GetLastEpochTime(ctx sdk.Context) (t time.Time, found bool) { 15 | store := ctx.KVStore(k.storeKey) 16 | bz := store.Get(types.LastEpochTimeKey) 17 | if bz == nil { 18 | return 19 | } 20 | var ts gogotypes.Timestamp 21 | k.cdc.MustUnmarshal(bz, &ts) 22 | var err error 23 | t, err = gogotypes.TimestampFromProto(&ts) 24 | if err != nil { 25 | panic(err) 26 | } 27 | found = true 28 | return 29 | } 30 | 31 | // SetLastEpochTime sets the last time the epoch ended. 32 | func (k Keeper) SetLastEpochTime(ctx sdk.Context, t time.Time) { 33 | store := ctx.KVStore(k.storeKey) 34 | ts, err := gogotypes.TimestampProto(t) 35 | if err != nil { 36 | panic(err) 37 | } 38 | bz := k.cdc.MustMarshal(ts) 39 | store.Set(types.LastEpochTimeKey, bz) 40 | } 41 | 42 | // AdvanceEpoch ends the current epoch. When an epoch ends, rewards 43 | // are distributed and queued staking coins become staked. 44 | func (k Keeper) AdvanceEpoch(ctx sdk.Context) error { 45 | if err := k.AllocateRewards(ctx); err != nil { 46 | return err 47 | } 48 | k.ProcessQueuedCoins(ctx) 49 | k.SetLastEpochTime(ctx, ctx.BlockTime()) 50 | 51 | return nil 52 | } 53 | 54 | // GetCurrentEpochDays returns the current epoch days(period). 55 | func (k Keeper) GetCurrentEpochDays(ctx sdk.Context) uint32 { 56 | var epochDays uint32 57 | store := ctx.KVStore(k.storeKey) 58 | bz := store.Get(types.CurrentEpochDaysKey) 59 | if bz == nil { 60 | // initialize with next epoch days 61 | epochDays = k.GetParams(ctx).NextEpochDays 62 | } else { 63 | val := gogotypes.UInt32Value{} 64 | if err := k.cdc.Unmarshal(bz, &val); err != nil { 65 | panic(err) 66 | } 67 | epochDays = val.GetValue() 68 | } 69 | return epochDays 70 | } 71 | 72 | // SetCurrentEpochDays sets the current epoch days(period). 73 | func (k Keeper) SetCurrentEpochDays(ctx sdk.Context, epochDays uint32) { 74 | store := ctx.KVStore(k.storeKey) 75 | bz := k.cdc.MustMarshal(&gogotypes.UInt32Value{Value: epochDays}) 76 | store.Set(types.CurrentEpochDaysKey, bz) 77 | } 78 | -------------------------------------------------------------------------------- /TECHNICAL-SETUP.md: -------------------------------------------------------------------------------- 1 | # Technical Setup 2 | 3 | To ensure you have a successful experience working with our farming module, we recommend this technical setup. 4 | 5 | ## Github Integration 6 | 7 | Click the GitHub icon in the sidebar for GitHub integration and follow the prompts. 8 | 9 | Clone the repos you work in 10 | 11 | - Fork or clone the https://github.com/tendermint/farming repository. 12 | 13 | Internal Tendermint users have different permissions, if you're not sure, fork the repo. 14 | 15 | ## Software Requirement 16 | 17 | To build the project: 18 | 19 | - [Golang](https://golang.org/dl/) v1.16 or higher 20 | - [make](https://www.gnu.org/software/make/) to use `Makefile` targets 21 | 22 | ## Development Environment Setup 23 | 24 | Setup git hooks for conventional commit. 25 | 26 | 1. Install [`pre-commit`](https://pre-commit.com/) 27 | 28 | 2. Run the following command: 29 | ```bash 30 | pre-commit install --hook-type commit-msg 31 | ``` 32 | 33 | 3. (Optional for macOS users) Install GNU `grep`: 34 | 35 | 4. Run the following command 36 | ```bash 37 | brew install grep 38 | ``` 39 | 40 | 5. Add the line to your shell profile: 41 | ```bash 42 | export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH" 43 | ``` 44 | 45 | Now, whenever you make a commit, the `pre-commit` hook will be run to check if the commit message conforms [Conventional Commit](https://www.conventionalcommits.org/) rule. 46 | 47 | ## Building 48 | 49 | To build the farming module node and command line client, run the `make build` command from the project's root folder. The output of the build will be generated in the `build` folder. 50 | 51 | For cross-builds use the standard `GOOS` and `GOARCH` env vars. i.e. to build for windows: 52 | 53 | ```bash 54 | GOOS=windows GOARCH=amd64 make build 55 | ``` 56 | 57 | ## Installation 58 | 59 | To install the node client on your machine, run `make install` command from the project's root folder. 60 | 61 | > 💡 you can also use the default `go` command to build the project, check the content of the [Makefile](https://github.com/tendermint/farming/blob/main/Makefile#L90) for reference 62 | 63 | ## Testing 64 | 65 | Run `make test-all` command to run tests. 66 | 67 | > 💡 you can also use the default `go` command to build the project, check the content of the [Makefile](https://github.com/tendermint/farming/blob/main/Makefile#L145) for reference 68 | 69 | -------------------------------------------------------------------------------- /x/farming/handler.go: -------------------------------------------------------------------------------- 1 | package farming 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 7 | 8 | "github.com/tendermint/farming/x/farming/keeper" 9 | "github.com/tendermint/farming/x/farming/types" 10 | ) 11 | 12 | func NewHandler(k keeper.Keeper) sdk.Handler { 13 | msgServer := keeper.NewMsgServerImpl(k) 14 | 15 | return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { 16 | ctx = ctx.WithEventManager(sdk.NewEventManager()) 17 | 18 | switch msg := msg.(type) { 19 | case *types.MsgCreateFixedAmountPlan: 20 | res, err := msgServer.CreateFixedAmountPlan(sdk.WrapSDKContext(ctx), msg) 21 | return sdk.WrapServiceResult(ctx, res, err) 22 | 23 | case *types.MsgCreateRatioPlan: 24 | res, err := msgServer.CreateRatioPlan(sdk.WrapSDKContext(ctx), msg) 25 | return sdk.WrapServiceResult(ctx, res, err) 26 | 27 | case *types.MsgStake: 28 | res, err := msgServer.Stake(sdk.WrapSDKContext(ctx), msg) 29 | return sdk.WrapServiceResult(ctx, res, err) 30 | 31 | case *types.MsgUnstake: 32 | res, err := msgServer.Unstake(sdk.WrapSDKContext(ctx), msg) 33 | return sdk.WrapServiceResult(ctx, res, err) 34 | 35 | case *types.MsgHarvest: 36 | res, err := msgServer.Harvest(sdk.WrapSDKContext(ctx), msg) 37 | return sdk.WrapServiceResult(ctx, res, err) 38 | 39 | case *types.MsgRemovePlan: 40 | res, err := msgServer.RemovePlan(sdk.WrapSDKContext(ctx), msg) 41 | return sdk.WrapServiceResult(ctx, res, err) 42 | 43 | case *types.MsgAdvanceEpoch: 44 | res, err := msgServer.AdvanceEpoch(sdk.WrapSDKContext(ctx), msg) 45 | return sdk.WrapServiceResult(ctx, res, err) 46 | 47 | default: 48 | return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) 49 | } 50 | } 51 | } 52 | 53 | // NewPublicPlanProposalHandler creates a governance handler to manage new proposal types. 54 | // It enables PublicPlanProposal to propose a plan creation / modification / deletion. 55 | func NewPublicPlanProposalHandler(k keeper.Keeper) govtypes.Handler { 56 | return func(ctx sdk.Context, content govtypes.Content) error { 57 | switch c := content.(type) { 58 | case *types.PublicPlanProposal: 59 | return keeper.HandlePublicPlanProposal(ctx, k, c) 60 | 61 | default: 62 | return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized farming proposal content type: %T", c) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/Explanation/ADR/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records (ADR) 2 | 3 | Use this location to record all high-level architecture decisions in the farming module. 4 | 5 | ### Definitions 6 | 7 | Within the context of an ADR we define the following: 8 | An Architectural Decision (**AD**) is a software design choice that addresses a functional or non-functional requirement that is architecturally significant. 9 | An Architecturally Significant Requirement (**ASR**) is a requirement that has a measurable effect on a software system’s architecture and quality. 10 | An Architectural Decision Record (**ADR**) captures a single AD, and is as often done when writing personal notes or meeting minutes. The collection of ADRs created and maintained in a project constitute its decision log. All these records are within the topic of Architectural Knowledge Management (AKM). 11 | 12 | You can read more about the ADR concept in the [Documenting architecture decisions, the Reverb way](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t) blog post. 13 | 14 | ## Rationale 15 | 16 | ADRs are intended to be the primary mechanism for proposing new feature designs and new processes, for collecting community input on an issue, and for documenting the design decisions. 17 | An ADR should provide: 18 | 19 | - Context on the relevant goals and the current state 20 | - Proposed changes to achieve the goals 21 | - Summary of pros and cons 22 | - References 23 | - Changelog 24 | 25 | Note the distinction between an ADR and a specification. The ADR provides the context, intuition, reasoning, and justification for a change in architecture, or for the architecture of something new. The specification is a summary of everything as it stands today. 26 | 27 | If recorded decisions turned out to be lacking the required substance, the process is to convene a discussion, record the new decisions here, and then modify the code to match. 28 | 29 | ## Creating new ADR 30 | 31 | Read about the [PROCESS](./PROCESS.md). 32 | 33 | #### Use RFC 2119 Keywords 34 | 35 | When writing ADRs, follow the same best practices for writing RFCs. When writing RFCs, key words are used to signify the requirements in the specification. These words are often capitalized: "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL. They are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). 36 | 37 | ## ADR Table of Contents 38 | 39 | - [ADR 002: Documentation Structure](./adr-002-docs-structure.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![codecov](https://codecov.io/gh/tendermint/farming/branch/main/graph/badge.svg)](https://codecov.io/gh/tendermint/farming?branch=main) 2 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/tendermint/farming)](https://pkg.go.dev/github.com/tendermint/farming) 3 | 4 | # Farming Module 5 | 6 | The farming module is a Cosmos SDK module that implements farming functionality, which provides farming rewards to participants called farmers. A primary use case is to use this module to provide incentives for liquidity pool investors for their pool participation. 7 | 8 | - see the [main](https://github.com/tendermint/farming/tree/main) branch for the latest 9 | - see [releases](https://github.com/tendermint/farming/releases) for the latest release 10 | 11 | ## Dependencies 12 | 13 | If you haven't already, install Golang by following the [official docs](https://golang.org/doc/install). Make sure that your `GOPATH` and `GOBIN` environment variables are properly set up. 14 | 15 | Requirement | Notes 16 | ----------- | ----------------- 17 | Go version | Go1.16 or higher 18 | Cosmos SDK | v0.44.5 or higher 19 | 20 | ## Installation 21 | 22 | ```bash 23 | # Use git to clone farming module source code and install `farmingd` 24 | git clone https://github.com/tendermint/farming.git 25 | cd farming 26 | make install 27 | ``` 28 | 29 | ## Getting Started 30 | 31 | To get started to the project, visit the [TECHNICAL-SETUP.md](./TECHNICAL-SETUP.md) docs. 32 | 33 | ## Documentation 34 | 35 | The farming module documentation is available in [docs](./docs) folder and technical specification is available in [specs](https://github.com/tendermint/farming/blob/main/x/farming/spec/README.md) folder. 36 | 37 | These are some of the documents that help you to quickly get you on board with the farming module. 38 | 39 | - [How to bootstrap a local network with farming module](./docs/Tutorials/localnet) 40 | - [How to use Command Line Interfaces](./docs/How-To/cli) 41 | - [How to use gRPC-gateway REST Routes](./docs/How-To) 42 | - [Demo for how to budget and farming modules](./docs/Tutorials/demo/budget_with_farming.md) 43 | 44 | ## Contributing 45 | 46 | We welcome contributions from everyone. The [main](https://github.com/tendermint/farming/tree/main) branch contains the development version of the code. You can branch of from main and create a pull request, or maintain your own fork and submit a cross-repository pull request. If you're not sure where to start check out [CONTRIBUTING.md](./CONTRIBUTING.md) for our guidelines & policies for how we develop farming module. Thank you to all those who have contributed to farming module! 47 | -------------------------------------------------------------------------------- /docs/Explanation/ADR/adr-template.md: -------------------------------------------------------------------------------- 1 | # ADR {ADR-NUMBER}: {TITLE} 2 | 3 | ## Changelog 4 | 5 | - {date}: {changelog} 6 | 7 | ## Status 8 | 9 | {DRAFT | PROPOSED} Not Implemented 10 | 11 | > For details on ADR workflow, see the [PROCESS](./PROCESS.md#adr-status) page. 12 | > Use DRAFT if the ADR is in a draft stage (draft PR) or PROPOSED if it's in review. 13 | 14 | ## Abstract 15 | 16 | > "If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the ADR. 17 | > A short (~200 word) description of the issue being addressed. 18 | 19 | ## Context 20 | 21 | > This section describes the forces at play, including technological, political, social, and project local. These forces are probably in tension describe them as such. The language in this section is value-neutral and just describes facts. The context should clearly explain the problem and motivation that the proposal aims to resolve. 22 | > {context body} 23 | 24 | ## Decision 25 | 26 | > This section describes our response to these forces. It is stated in full sentences, with active voice. "We will ..." 27 | > {decision body} 28 | 29 | ## Consequences 30 | 31 | > This section describes the resulting context after applying the decision. List all consequences here, taking care not to list only the "positive" consequences. A particular decision may have positive, negative, and neutral consequences, but all of the consesquences affect the team and project in the future. 32 | 33 | ### Backwards Compatibility 34 | 35 | > All ADRs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The ADR must explain how the author proposes to deal with these incompatibilities. ADR submissions without a sufficient backwards compatibility treatise may be rejected outright. 36 | 37 | ### Positive 38 | 39 | {positive consequences} 40 | 41 | ### Negative 42 | 43 | {negative consequences} 44 | 45 | ### Neutral 46 | 47 | {neutral consequences} 48 | 49 | ## Further Discussions 50 | 51 | While an ADR is in the DRAFT or PROPOSED stage, this section contains a summary of issues to be solved in future iterations. The issues summarized here can reference comments from a pull request discussion. 52 | Later, this section can optionally list ideas or improvements the author or reviewers found during the analysis of this ADR. 53 | 54 | ## Test Cases [optional] 55 | 56 | Test cases for an implementation are mandatory for ADRs that are affecting consensus changes. Other ADRs can choose to include links to test cases if applicable. 57 | 58 | ## References 59 | 60 | - {reference link} -------------------------------------------------------------------------------- /x/farming/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 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 10 | ) 11 | 12 | // RegisterLegacyAminoCodec registers the necessary x/farming interfaces and concrete types 13 | // on the provided LegacyAmino codec. These types are used for Amino JSON serialization. 14 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 15 | cdc.RegisterInterface((*PlanI)(nil), nil) 16 | cdc.RegisterConcrete(&MsgCreateFixedAmountPlan{}, "farming/MsgCreateFixedAmountPlan", nil) 17 | cdc.RegisterConcrete(&MsgCreateRatioPlan{}, "farming/MsgCreateRatioPlan", nil) 18 | cdc.RegisterConcrete(&MsgStake{}, "farming/MsgStake", nil) 19 | cdc.RegisterConcrete(&MsgUnstake{}, "farming/MsgUnstake", nil) 20 | cdc.RegisterConcrete(&MsgHarvest{}, "farming/MsgHarvest", nil) 21 | cdc.RegisterConcrete(&MsgRemovePlan{}, "farming/MsgRemovePlan", nil) 22 | cdc.RegisterConcrete(&FixedAmountPlan{}, "farming/FixedAmountPlan", nil) 23 | cdc.RegisterConcrete(&RatioPlan{}, "farming/RatioPlan", nil) 24 | cdc.RegisterConcrete(&PublicPlanProposal{}, "farming/PublicPlanProposal", nil) 25 | } 26 | 27 | // RegisterInterfaces registers the x/farming interfaces types with the interface registry 28 | func RegisterInterfaces(registry types.InterfaceRegistry) { 29 | registry.RegisterImplementations( 30 | (*sdk.Msg)(nil), 31 | &MsgCreateFixedAmountPlan{}, 32 | &MsgCreateRatioPlan{}, 33 | &MsgStake{}, 34 | &MsgUnstake{}, 35 | &MsgHarvest{}, 36 | &MsgRemovePlan{}, 37 | ) 38 | 39 | registry.RegisterImplementations( 40 | (*govtypes.Content)(nil), 41 | &PublicPlanProposal{}, 42 | ) 43 | 44 | registry.RegisterInterface( 45 | "cosmos.farming.v1beta1.PlanI", 46 | (*PlanI)(nil), 47 | &FixedAmountPlan{}, 48 | &RatioPlan{}, 49 | ) 50 | 51 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 52 | } 53 | 54 | var ( 55 | amino = codec.NewLegacyAmino() 56 | 57 | // ModuleCdc references the global x/farming module codec. Note, the codec 58 | // should ONLY be used in certain instances of tests and for JSON encoding as Amino 59 | // is still used for that purpose. 60 | // 61 | // The actual codec used for serialization should be provided to x/farming and 62 | // defined at the application level. 63 | ModuleCdc = codec.NewAminoCodec(amino) 64 | ) 65 | 66 | func init() { 67 | RegisterLegacyAminoCodec(amino) 68 | cryptocodec.RegisterCrypto(amino) 69 | amino.Seal() 70 | } 71 | -------------------------------------------------------------------------------- /client/docs/swagger-ui/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 68 | -------------------------------------------------------------------------------- /docs/Explanation/README.md: -------------------------------------------------------------------------------- 1 | # Explanation Documentation 2 | 3 | How to use the farming module Explanation Documentation. 4 | 5 | - [Explanation Documentation](#explanation-documentation) 6 | - [Introduction](#introduction) 7 | - [Layout](#layout) 8 | - [Architecture Decisions Records (ADRs)](#architecture-decisions-records-adrs) 9 | - [Discussions](#discussions) 10 | - [Contributing](#contributing) 11 | - [Reference](#reference) 12 | 13 | 14 | ## Introduction 15 | 16 | This section contains **Explanation documentation** for farming module. This content is intended to help readers with their **understanding of farming module and related topics**. It is intended to be discursive, thoughtful, interesting, and occasionally educational. The content includes analysis and review of alternative approaches. 17 | 18 | For further background information please see [the ADR relating to the documentation structure](./ADR/adr-002-docs-structure.md). 19 | 20 | ## Layout 21 | 22 | The scope and structure of the Explanation documentation follows this layout. 23 | 24 | ### Architecture Decisions Records (ADRs) 25 | 26 | ADRs are the mechanism for contributors to raise design proposals. In turn, the ADRs explain for subsequent contributors the rationale behind farming module design and implementation. For example, [ADR 002: Documentation Structure](./ADR/adr-002-docs-structure.md) explains why the farming module documentation structure was chosen. 27 | 28 | See the Architecture Decision Records (ADR) [README](./ADR/README.md) file for more details about how to raise and propose an ADR. 29 | 30 | ## Discussions 31 | 32 | The explanation content includes articles, topics, and so on, and also includes discussion on relevant channels, including [Pull Requests](https://github.com/tendermint/farming/pulls) and [Issues](https://github.com/tendermint/farming/issues). Important Pull Requests are listed in this document. 33 | 34 | Future: farming module currently doesn't have a Discord or Telegram Channel. When we do, the links will be added here as well 35 | ## Contributing 36 | 37 | * The Explanation content should be dry, clear, and terse in style. 38 | * All documentation is written following [Google Documentation Best Practice](https://google.github.io/styleguide/docguide/best_practices.html) 39 | * Autogenerate documentation from the code whenever possible. 40 | * Raise a PR for all documentation changes 41 | * Follow our [Code of Conduct](../../CONTRIBUTING.md) 42 | 43 | ## Reference 44 | 45 | - [Google Style Guide for Markdown](https://github.com/google/styleguide/blob/gh-pages/docguide/style.md) 46 | - [Write the Docs global community](https://www.writethedocs.org/) 47 | - [Write the Docs Code of Conduct](https://www.writethedocs.org/code-of-conduct/#the-principles) -------------------------------------------------------------------------------- /x/farming/simulation/genesis_test.go: -------------------------------------------------------------------------------- 1 | package simulation_test 2 | 3 | import ( 4 | "encoding/json" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/cosmos/cosmos-sdk/types/module" 14 | simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 15 | 16 | "github.com/tendermint/farming/x/farming/simulation" 17 | "github.com/tendermint/farming/x/farming/types" 18 | ) 19 | 20 | // TestRandomizedGenState tests the normal scenario of applying RandomizedGenState. 21 | // Abnormal scenarios are not tested here. 22 | func TestRandomizedGenState(t *testing.T) { 23 | interfaceRegistry := codectypes.NewInterfaceRegistry() 24 | cdc := codec.NewProtoCodec(interfaceRegistry) 25 | s := rand.NewSource(1) 26 | r := rand.New(s) 27 | 28 | simState := module.SimulationState{ 29 | AppParams: make(simtypes.AppParams), 30 | Cdc: cdc, 31 | Rand: r, 32 | NumBonded: 3, 33 | Accounts: simtypes.RandomAccounts(r, 3), 34 | InitialStake: 1000, 35 | GenState: make(map[string]json.RawMessage), 36 | } 37 | 38 | simulation.RandomizedGenState(&simState) 39 | 40 | var genState types.GenesisState 41 | simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) 42 | 43 | dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(36122540))) 44 | dec3 := uint32(5) 45 | dec4 := "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" 46 | dec5 := uint32(1347) 47 | 48 | require.Equal(t, dec1, genState.Params.PrivatePlanCreationFee) 49 | require.Equal(t, dec3, genState.Params.NextEpochDays) 50 | require.Equal(t, dec4, genState.Params.FarmingFeeCollector) 51 | require.Equal(t, dec5, genState.Params.MaxNumPrivatePlans) 52 | } 53 | 54 | // TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. 55 | func TestRandomizedGenState1(t *testing.T) { 56 | interfaceRegistry := codectypes.NewInterfaceRegistry() 57 | cdc := codec.NewProtoCodec(interfaceRegistry) 58 | 59 | s := rand.NewSource(1) 60 | r := rand.New(s) 61 | 62 | // all these tests will panic 63 | tests := []struct { 64 | simState module.SimulationState 65 | panicMsg string 66 | }{ 67 | { // panic => reason: incomplete initialization of the simState 68 | module.SimulationState{}, "invalid memory address or nil pointer dereference"}, 69 | { // panic => reason: incomplete initialization of the simState 70 | module.SimulationState{ 71 | AppParams: make(simtypes.AppParams), 72 | Cdc: cdc, 73 | Rand: r, 74 | }, "assignment to entry in nil map"}, 75 | } 76 | 77 | for _, tt := range tests { 78 | require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /x/farming/spec/07_params.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Parameters 4 | 5 | The `farming` module contains the following parameters: 6 | 7 | | Key | Type | Example | 8 | |-------------------------|-----------|---------------------------------------------------------------------| 9 | | PrivatePlanCreationFee | sdk.Coins | [{"denom":"stake","amount":"1000000000"}] | 10 | | NextEpochDays | uint32 | 1 | 11 | | FarmingFeeCollector | string | "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" | 12 | | DelayedStakingGasFee | sdk.Gas | 60000 | 13 | | MaxNumPrivatePlans | uint32 | 10000 | 14 | 15 | 16 | ## PrivatePlanCreationFee 17 | 18 | Fee paid to create a private farming plan. This fee prevents spamming attack and is reserved in the FarmingFeeCollector. If the plan creator removes the plan, this fee will be refunded to the creator. 19 | 20 | ## NextEpochDays 21 | 22 | `NextEpochDays` is the epoch length in number of days. Internally, the farming module uses `CurrentEpochDays` parameter to process staking and reward distribution in end-blocker because using `NextEpochDays` directly will affect farming rewards allocation. 23 | 24 | ## FarmingFeeCollector 25 | 26 | A farming fee collector is a module account address that collects farming fees, such as staking creation fee and private plan creation fee. 27 | 28 | ## DelayedStakingGasFee 29 | 30 | Since the farming module has adopted F1 reward distribution, changes in staked coins cause withdrawal of accrued rewards. 31 | 32 | In addition, the farming module employs a concept of delayed staking. This means that when a farmer stakes coins through `MsgStake`, staked coins are not modified immediately. 33 | 34 | Instead, at the end of the epoch, queued staking coins becomes staked and the rewards are withdrawn. For this reason, the `DelayedStakingGasFee` parameter is available to impose gas fees for the future call of `WithdrawRewards` if a farmer has any staked coins with same 35 | denom of newly staked coin. 36 | 37 | ## MaxNumPrivatePlans 38 | 39 | The maximum number of private plans that are allowed to be created. 40 | It does not include terminated plans. 41 | 42 | # Global constants 43 | 44 | There are some global constants defined in `x/farming/types/params.go`. 45 | 46 | ## PrivatePlanMaxNumDenoms 47 | 48 | This is the maximum number of denoms in a private plan's staking coin weights and epoch amount. 49 | It's set to `50`. 50 | 51 | ## PublicPlanMaxNumDenoms 52 | 53 | This is the maximum number of denoms in a public plan's staking coin weights and epoch amount. 54 | It's set to `500`. 55 | -------------------------------------------------------------------------------- /third_party/proto/tendermint/types/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/tendermint/tendermint/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 | -------------------------------------------------------------------------------- /x/farming/client/cli/utils.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "time" 7 | 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | 11 | "github.com/tendermint/farming/x/farming/types" 12 | ) 13 | 14 | // PrivateFixedPlanRequest defines CLI request for a private fixed plan. 15 | type PrivateFixedPlanRequest struct { 16 | Name string `json:"name"` 17 | StakingCoinWeights sdk.DecCoins `json:"staking_coin_weights"` 18 | StartTime time.Time `json:"start_time"` 19 | EndTime time.Time `json:"end_time"` 20 | EpochAmount sdk.Coins `json:"epoch_amount"` 21 | } 22 | 23 | // PrivateRatioPlanRequest defines CLI request for a private ratio plan. 24 | type PrivateRatioPlanRequest struct { 25 | Name string `json:"name"` 26 | StakingCoinWeights sdk.DecCoins `json:"staking_coin_weights"` 27 | StartTime time.Time `json:"start_time"` 28 | EndTime time.Time `json:"end_time"` 29 | EpochRatio sdk.Dec `json:"epoch_ratio"` 30 | } 31 | 32 | // ParsePrivateFixedPlan reads and parses a PrivateFixedPlanRequest from a file. 33 | func ParsePrivateFixedPlan(file string) (PrivateFixedPlanRequest, error) { 34 | plan := PrivateFixedPlanRequest{} 35 | 36 | contents, err := ioutil.ReadFile(file) 37 | if err != nil { 38 | return plan, err 39 | } 40 | 41 | if err = json.Unmarshal(contents, &plan); err != nil { 42 | return plan, err 43 | } 44 | 45 | return plan, nil 46 | } 47 | 48 | // ParsePrivateRatioPlan reads and parses a PrivateRatioPlanRequest from a file. 49 | func ParsePrivateRatioPlan(file string) (PrivateRatioPlanRequest, error) { 50 | plan := PrivateRatioPlanRequest{} 51 | 52 | contents, err := ioutil.ReadFile(file) 53 | if err != nil { 54 | return plan, err 55 | } 56 | 57 | if err = json.Unmarshal(contents, &plan); err != nil { 58 | return plan, err 59 | } 60 | 61 | return plan, nil 62 | } 63 | 64 | // ParsePublicPlanProposal reads and parses a PublicPlanProposal from a file. 65 | func ParsePublicPlanProposal(cdc codec.JSONCodec, proposalFile string) (types.PublicPlanProposal, error) { 66 | proposal := types.PublicPlanProposal{} 67 | 68 | contents, err := ioutil.ReadFile(proposalFile) 69 | if err != nil { 70 | return proposal, err 71 | } 72 | 73 | if err = cdc.UnmarshalJSON(contents, &proposal); err != nil { 74 | return proposal, err 75 | } 76 | 77 | return proposal, nil 78 | } 79 | 80 | // String returns a human readable string representation of the request. 81 | func (req PrivateFixedPlanRequest) String() string { 82 | result, err := json.Marshal(&req) 83 | if err != nil { 84 | panic(err) 85 | } 86 | return string(result) 87 | } 88 | 89 | // String returns a human readable string representation of the request. 90 | func (req PrivateRatioPlanRequest) String() string { 91 | result, err := json.Marshal(&req) 92 | if err != nil { 93 | panic(err) 94 | } 95 | return string(result) 96 | } 97 | -------------------------------------------------------------------------------- /third_party/proto/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | 16 | syntax = "proto3"; 17 | 18 | package google.api; 19 | 20 | import "google/protobuf/any.proto"; 21 | 22 | option cc_enable_arenas = true; 23 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HttpBodyProto"; 26 | option java_package = "com.google.api"; 27 | option objc_class_prefix = "GAPI"; 28 | 29 | // Message that represents an arbitrary HTTP body. It should only be used for 30 | // payload formats that can't be represented as JSON, such as raw binary or 31 | // an HTML page. 32 | // 33 | // 34 | // This message can be used both in streaming and non-streaming API methods in 35 | // the request as well as the response. 36 | // 37 | // It can be used as a top-level request field, which is convenient if one 38 | // wants to extract parameters from either the URL or HTTP template into the 39 | // request fields and also want access to the raw HTTP body. 40 | // 41 | // Example: 42 | // 43 | // message GetResourceRequest { 44 | // // A unique request id. 45 | // string request_id = 1; 46 | // 47 | // // The raw HTTP body is bound to this field. 48 | // google.api.HttpBody http_body = 2; 49 | // } 50 | // 51 | // service ResourceService { 52 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 53 | // rpc UpdateResource(google.api.HttpBody) returns 54 | // (google.protobuf.Empty); 55 | // } 56 | // 57 | // Example with streaming methods: 58 | // 59 | // service CaldavService { 60 | // rpc GetCalendar(stream google.api.HttpBody) 61 | // returns (stream google.api.HttpBody); 62 | // rpc UpdateCalendar(stream google.api.HttpBody) 63 | // returns (stream google.api.HttpBody); 64 | // } 65 | // 66 | // Use of this type only changes how the request and response bodies are 67 | // handled, all other features will continue to work unchanged. 68 | message HttpBody { 69 | // The HTTP Content-Type header value specifying the content type of the body. 70 | string content_type = 1; 71 | 72 | // The HTTP request/response body as raw binary. 73 | bytes data = 2; 74 | 75 | // Application specific response metadata. Must be set in the first response 76 | // for streaming APIs. 77 | repeated google.protobuf.Any extensions = 3; 78 | } -------------------------------------------------------------------------------- /docs/Tutorials/localnet/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Localnet 3 | Description: A tutorial of how to build `farmingd` and bootstrap local network. 4 | --- 5 | 6 | ### Get farming module source code 7 | 8 | ```bash 9 | git clone https://github.com/tendermint/farming.git 10 | cd farming 11 | make install 12 | ``` 13 | 14 | ### Boostrap 15 | 16 | The following script is prepared to bootstrap a single chain with a single validator in your local machine. Copy the script and run them in your terminal. 17 | 18 | ```bash 19 | # Configure variables 20 | export BINARY=farmingd 21 | export HOME_FARMINGAPP=$HOME/.farmingapp 22 | export CHAIN_ID=localnet 23 | export VALIDATOR_1="struggle panic room apology luggage game screen wing want lazy famous eight robot picture wrap act uphold grab away proud music danger naive opinion" 24 | export USER_1="guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host" 25 | export USER_2="fuel obscure melt april direct second usual hair leave hobby beef bacon solid drum used law mercy worry fat super must ritual bring faculty" 26 | export VALIDATOR_1_GENESIS_COINS=10000000000stake,10000000000uatom,10000000000uusd 27 | export USER_1_GENESIS_COINS=10000000000stake,10000000000uatom,10000000000uusd 28 | export USER_2_GENESIS_COINS=10000000000stake,10000000000poolD35A0CC16EE598F90B044CE296A405BA9C381E38837599D96F2F70C2F02A23A4 29 | 30 | # Bootstrap 31 | $BINARY init $CHAIN_ID --chain-id $CHAIN_ID 32 | echo $VALIDATOR_1 | $BINARY keys add val1 --keyring-backend test --recover 33 | echo $USER_1 | $BINARY keys add user1 --keyring-backend test --recover 34 | echo $USER_2 | $BINARY keys add user2 --keyring-backend test --recover 35 | $BINARY add-genesis-account $($BINARY keys show val1 --keyring-backend test -a) $VALIDATOR_1_GENESIS_COINS 36 | $BINARY add-genesis-account $($BINARY keys show user1 --keyring-backend test -a) $USER_1_GENESIS_COINS 37 | $BINARY add-genesis-account $($BINARY keys show user2 --keyring-backend test -a) $USER_2_GENESIS_COINS 38 | $BINARY gentx val1 100000000stake --chain-id $CHAIN_ID --keyring-backend test 39 | $BINARY collect-gentxs 40 | 41 | # Check OS for sed -i option value 42 | export SED_I="" 43 | if [[ "$OSTYPE" == "darwin"* ]]; then 44 | export SED_I="''" 45 | fi 46 | 47 | # Modify app.toml 48 | sed -i $SED_I 's/enable = false/enable = true/g' $HOME_FARMINGAPP/config/app.toml 49 | sed -i $SED_I 's/swagger = false/swagger = true/g' $HOME_FARMINGAPP/config/app.toml 50 | 51 | # (Optional) Modify governance proposal for testing public plan proposal 52 | sed -i $SED_I 's%"amount": "10000000"%"amount": "1"%g' $HOME_FARMINGAPP/config/genesis.json 53 | sed -i $SED_I 's%"quorum": "0.334000000000000000",%"quorum": "0.000000000000000001",%g' $HOME_FARMINGAPP/config/genesis.json 54 | sed -i $SED_I 's%"threshold": "0.500000000000000000",%"threshold": "0.000000000000000001",%g' $HOME_FARMINGAPP/config/genesis.json 55 | sed -i $SED_I 's%"voting_period": "172800s"%"voting_period": "60s"%g' $HOME_FARMINGAPP/config/genesis.json 56 | 57 | # Start 58 | $BINARY start 59 | ``` 60 | -------------------------------------------------------------------------------- /contrib/devtools/Makefile: -------------------------------------------------------------------------------- 1 | ### 2 | # Find OS and Go environment 3 | # GO contains the Go binary 4 | # FS contains the OS file separator 5 | ### 6 | ifeq ($(OS),Windows_NT) 7 | GO := $(shell where go.exe 2> NUL) 8 | FS := "\\" 9 | else 10 | GO := $(shell command -v go 2> /dev/null) 11 | FS := "/" 12 | endif 13 | 14 | ifeq ($(GO),) 15 | $(error could not find go. Is it in PATH? $(GO)) 16 | endif 17 | 18 | ############################################################################### 19 | ### Functions ### 20 | ############################################################################### 21 | 22 | go_get = $(if $(findstring Windows_NT,$(OS)),\ 23 | IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\ 24 | IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\ 25 | ,\ 26 | mkdir -p $(GITHUBDIR)$(FS)$(1) &&\ 27 | (test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\ 28 | )\ 29 | cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3) 30 | 31 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 32 | mkfile_dir := $(shell cd $(shell dirname $(mkfile_path)); pwd) 33 | 34 | 35 | ############################################################################### 36 | ### Tools ### 37 | ############################################################################### 38 | 39 | PREFIX ?= /usr/local 40 | BIN ?= $(PREFIX)/bin 41 | UNAME_S ?= $(shell uname -s) 42 | UNAME_M ?= $(shell uname -m) 43 | 44 | GOPATH ?= $(shell $(GO) env GOPATH) 45 | GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com 46 | 47 | BUF_VERSION ?= 0.11.0 48 | 49 | TOOLS_DESTDIR ?= $(GOPATH)/bin 50 | STATIK = $(TOOLS_DESTDIR)/statik 51 | RUNSIM = $(TOOLS_DESTDIR)/runsim 52 | 53 | tools: tools-stamp 54 | tools-stamp: statik runsim 55 | # Create dummy file to satisfy dependency and avoid 56 | # rebuilding when this Makefile target is hit twice 57 | # in a row. 58 | touch $@ 59 | 60 | # Install the runsim binary with a temporary workaround of entering an outside 61 | # directory as the "go get" command ignores the -mod option and will polute the 62 | # go.{mod, sum} files. 63 | # 64 | # ref: https://github.com/golang/go/issues/30515 65 | statik: $(STATIK) 66 | $(STATIK): 67 | @echo "Installing statik..." 68 | @(cd /tmp && go get github.com/rakyll/statik@v0.1.6) 69 | 70 | # Install the runsim binary with a temporary workaround of entering an outside 71 | # directory as the "go get" command ignores the -mod option and will polute the 72 | # go.{mod, sum} files. 73 | # 74 | # ref: https://github.com/golang/go/issues/30515 75 | runsim: $(RUNSIM) 76 | $(RUNSIM): 77 | @echo "Installing runsim..." 78 | @(cd /tmp && go get github.com/cosmos/tools/cmd/runsim@v1.0.0) 79 | 80 | tools-clean: 81 | rm -f $(STATIK) $(GOLANGCI_LINT) $(RUNSIM) 82 | rm -f tools-stamp 83 | 84 | .PHONY: tools-clean statik runsim 85 | -------------------------------------------------------------------------------- /scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Set localnet settings 4 | BINARY=farmingd 5 | CHAIN_ID=localnet 6 | CHAIN_DIR=./data 7 | RPC_PORT=26657 8 | GRPC_PORT=9090 9 | MNEMONIC_1="guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host" 10 | MNEMONIC_2="friend excite rough reopen cover wheel spoon convince island path clean monkey play snow number walnut pull lock shoot hurry dream divide concert discover" 11 | MNEMONIC_3="fuel obscure melt april direct second usual hair leave hobby beef bacon solid drum used law mercy worry fat super must ritual bring faculty" 12 | GENESIS_COINS=10000000000stake,10000000000token,10000000000atom 13 | 14 | # Stop process if it is already running 15 | if pgrep -x "$BINARY" >/dev/null; then 16 | echo "Terminating $BINARY..." 17 | killall farmingd 18 | fi 19 | 20 | # Remove previous data 21 | rm -rf $CHAIN_DIR/$CHAIN_ID 22 | 23 | if ! mkdir -p $CHAIN_DIR/$CHAIN_ID 2>/dev/null; then 24 | echo "Failed to create chain folder. Aborting..." 25 | exit 1 26 | fi 27 | 28 | echo "Initializing $CHAIN_ID..." 29 | $BINARY --home $CHAIN_DIR/$CHAIN_ID init test --chain-id=$CHAIN_ID 30 | 31 | echo "Adding genesis accounts..." 32 | echo $MNEMONIC_1 | $BINARY keys add validator --home $CHAIN_DIR/$CHAIN_ID --recover --keyring-backend=test 33 | echo $MNEMONIC_2 | $BINARY keys add user1 --home $CHAIN_DIR/$CHAIN_ID --recover --keyring-backend=test 34 | echo $MNEMONIC_3 | $BINARY keys add user2 --home $CHAIN_DIR/$CHAIN_ID --recover --keyring-backend=test 35 | $BINARY add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show validator --keyring-backend test -a) $GENESIS_COINS --home $CHAIN_DIR/$CHAIN_ID 36 | $BINARY add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show user1 --keyring-backend test -a) $GENESIS_COINS --home $CHAIN_DIR/$CHAIN_ID 37 | $BINARY add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show user2 --keyring-backend test -a) $GENESIS_COINS --home $CHAIN_DIR/$CHAIN_ID 38 | 39 | echo "Creating and collecting gentx..." 40 | $BINARY gentx validator 1000000000stake --home $CHAIN_DIR/$CHAIN_ID --chain-id $CHAIN_ID --keyring-backend test 41 | $BINARY collect-gentxs --home $CHAIN_DIR/$CHAIN_ID 42 | 43 | echo "Change settings in config.toml file..." 44 | sed -i '' 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPC_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml 45 | sed -i '' 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAIN_ID/config/config.toml 46 | sed -i '' 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAIN_ID/config/config.toml 47 | sed -i '' 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAIN_ID/config/config.toml 48 | sed -i '' 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAIN_ID/config/app.toml 49 | sed -i '' 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAIN_ID/config/app.toml 50 | 51 | echo "Starting $CHAIN_ID in $CHAIN_DIR..." 52 | echo "Log file is located at $CHAIN_DIR/$CHAIN_ID.log" 53 | $BINARY start --home $CHAIN_DIR/$CHAIN_ID --pruning=nothing --grpc.address="0.0.0.0:$GRPC_PORT" > $CHAIN_DIR/$CHAIN_ID.log 2>&1 & -------------------------------------------------------------------------------- /docs/Explanation/ADR/PROCESS.md: -------------------------------------------------------------------------------- 1 | # ADR Creation Process 2 | 3 | 1. Copy the `adr-template.md` file. Use the following filename pattern: `adr-next_number-title.md` 4 | 5 | 2. Create a draft Pull Request if you want to get an early feedback. 6 | 7 | 3. Make sure the context and a solution is clear and well documented. 8 | 9 | 4. Add an entry to a list in the [README](./README.md) file. 10 | 11 | 5. Create a Pull Request to propose a new ADR. 12 | 13 | ## ADR life cycle 14 | 15 | ADR creation is an **iterative** process. Instead of trying to solve all decisions in a single ADR pull request, we MUST initially understand the problem and collect feedback by having conversations in a GitHub Issue. 16 | 17 | 1. Every ADR proposal SHOULD start with a [new GitHub issue](https://github.com/tendermint/starport/issues/new/choose) or be a result of existing Issues. The Issue must contain a brief proposal summary. 18 | 19 | 2. After the motivation is validated, create a new document that is on the `adr-template.md`. 20 | 21 | 3. An ADR solution doesn't have to arrive to the `main` branch with an _accepted_ status in a single PR. If the motivation is clear and the solution is sound, we SHOULD be able to merge PRs iteratively and keep a _proposed_ status. It's preferable to have an iterative approach rather than long, not merged Pull Requests. 22 | 23 | 4. If a _proposed_ ADR is merged, then the outstanding changes must be clearly documented in outstanding issues in ADR document notes or in a GitHub Issue. 24 | 25 | 5. The PR SHOULD always be merged. In the case of a faulty ADR, we still prefer to merge it with a _rejected_ status. The only time the ADR SHOULD NOT be merged is if the author abandons it. 26 | 27 | 6. Merged ADRs SHOULD NOT be pruned. 28 | 29 | ### ADR status 30 | 31 | Status has two components: 32 | 33 | ``` 34 | {CONSENSUS STATUS} {IMPLEMENTATION STATUS} 35 | ``` 36 | 37 | IMPLEMENTATION STATUS is either `Implemented` or `Not Implemented`. 38 | 39 | #### Consensus Status 40 | 41 | ``` 42 | DRAFT -> PROPOSED -> LAST CALL yyyy-mm-dd -> ACCEPTED | REJECTED -> SUPERSEEDED by ADR-xxx 43 | \ | 44 | \ | 45 | v v 46 | ABANDONED 47 | ``` 48 | 49 | + `DRAFT`: [optional] an ADR which is work in progress, not being ready for a general review. This is to present an early work and get an early feedback in a Draft Pull Request form. 50 | 51 | + `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached an agreed yet. 52 | 53 | + `LAST CALL `: [optional] clear notify that we are close to accept updates. Changing a status to `LAST CALL` means that social consensus (of Budge module maintainers) has been reached and we still want to give it a time to let the community react or analyze. 54 | 55 | + `ACCEPTED`: ADR which will represent a currently implemented or to be implemented architecture design. 56 | 57 | + `REJECTED`: ADR can go from PROPOSED or ACCEPTED to rejected if the consensus among project stakeholders will decide so. 58 | 59 | + `SUPERSEEDED by ADR-xxx`: ADR which has been superseded by a new ADR. 60 | 61 | + `ABANDONED`: the ADR is no longer pursued by the original authors. 62 | 63 | ## Language used in ADR 64 | 65 | + Write the context/background in the present tense. 66 | 67 | + Avoid using a first, personal form. -------------------------------------------------------------------------------- /x/farming/spec/03_state_transitions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # State Transitions 4 | 5 | This document describes the state transaction operations for the farming module. 6 | 7 | ## Plans 8 | 9 | As stated in [Concepts](01_concepts.md), both public and private farming plans are available in the `farming` module: 10 | 11 | - A public farming plan can be created only through governance proposal. 12 | - A private farming plan can be created with any account. 13 | 14 | 15 | ```go 16 | // PlanType enumerates the valid types of a plan. 17 | type PlanType int32 18 | 19 | const ( 20 | // PLAN_TYPE_UNSPECIFIED defines the default plan type. 21 | PlanTypeNil PlanType = 0 22 | // PLAN_TYPE_PUBLIC defines the public plan type. 23 | PlanTypePublic PlanType = 1 24 | // PLAN_TYPE_PRIVATE defines the private plan type. 25 | PlanTypePrivate PlanType = 2 26 | ) 27 | ``` 28 | 29 | ## Stake 30 | 31 | When a farmer stakes an amount of coins, the following state transitions occur: 32 | 33 | - Reserves the amount of coins to the staking reserve account for each staking coin denom `address.Module(ModuleName, []byte("StakingReserveAcc|"+stakingCoinDenom))` 34 | - Creates `QueuedStaking` object and stores the staking coins in `QueueStaking`, which then wait in a queue until the end of epoch to move to the `Staking` object 35 | - Imposes more gas if the farmer already has `Staking` with the same coin denom. See [Parameters](07_params.md#DelayedStakingGasFee) for details. 36 | 37 | ## Unstake 38 | 39 | When a farmer unstakes an amount of coins, the following state transitions occur: 40 | 41 | - Adds `Staking` and `QueueStaking` amounts to see if the unstaking amount is sufficient 42 | - Automatically withdraws rewards for the coin denom that are accumulated over the last epochs 43 | - Subtracts the unstaking amount of coins from `QueueStaking` first, and if not sufficient then subtracts from `Staking` 44 | - Releases the unstaking amount of coins to the farmer 45 | 46 | ## Harvest (Reward Withdrawal) 47 | 48 | - Calculates `CumulativeUnitRewards` in `HistoricalRewards` object in order to get the rewards for the staking coin denom that are accumulated over the last epochs 49 | - Releases the accumulated rewards to the farmer if it is not zero and decreases the `OutstandingRewards` 50 | - Sets `StartingEpoch` in `Staking` object 51 | 52 | ## Reward Allocation 53 | 54 | If the sum of total calculated `EpochAmount` (or `EpochRatio` multiplied by the farming pool balance) exceeds the farming pool balance, then skip the reward allocation for that epoch. 55 | 56 | For each [abci end block call](https://docs.cosmos.network/master/modules/staking/05_end_block.html), the operations to update the rewards allocation are: 57 | 58 | ++ https://github.com/tendermint/farming/blob/69db071ce30b99617b8ba9bb6efac76e74cd100b/x/farming/keeper/reward.go#L363-L426 59 | 60 | - Calculates rewards allocation information for the end of the current epoch depending on plan type `FixedAmountPlan` or `RatioPlan` 61 | - Distributes total allocated coins from each plan’s farming pool address `FarmingPoolAddress` to the rewards reserve pool account `RewardsReserveAcc` 62 | - Calculates staking coin weight for each denom in each plan and gets the unit rewards by denom 63 | - Updates `HistoricalRewards` and `CurrentEpoch` based on the allocation information 64 | - Deletes `QueueStaking` object after moving `QueueCoins` to `StakedCoins` in the `Staking` object -------------------------------------------------------------------------------- /x/farming/simulation/genesis.go: -------------------------------------------------------------------------------- 1 | package simulation 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | "math/rand" 7 | 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 | 12 | "github.com/tendermint/farming/x/farming/types" 13 | ) 14 | 15 | // Simulation parameter constants. 16 | const ( 17 | PrivatePlanCreationFee = "private_plan_creation_fee" 18 | NextEpochDays = "next_epoch_days" 19 | FarmingFeeCollector = "farming_fee_collector" 20 | CurrentEpochDays = "current_epoch_days" 21 | MaxNumPrivatePlans = "max_num_private_plans" 22 | ) 23 | 24 | // GenPrivatePlanCreationFee return randomized private plan creation fee. 25 | func GenPrivatePlanCreationFee(r *rand.Rand) sdk.Coins { 26 | return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 100_000_000)))) 27 | } 28 | 29 | // GenNextEpochDays return default next epoch days. 30 | func GenNextEpochDays(r *rand.Rand) uint32 { 31 | return uint32(simulation.RandIntBetween(r, int(types.DefaultNextEpochDays), 10)) 32 | } 33 | 34 | // GenCurrentEpochDays returns current epoch days. 35 | func GenCurrentEpochDays(r *rand.Rand) uint32 { 36 | return uint32(simulation.RandIntBetween(r, int(types.DefaultCurrentEpochDays), 10)) 37 | } 38 | 39 | // GenFarmingFeeCollector returns default farming fee collector. 40 | func GenFarmingFeeCollector(r *rand.Rand) string { 41 | return types.DefaultFarmingFeeCollector.String() 42 | } 43 | 44 | // GenMaxNumPrivatePlans returns a randomized value for MaxNumPrivatePlans param. 45 | func GenMaxNumPrivatePlans(r *rand.Rand) uint32 { 46 | return uint32(simulation.RandIntBetween(r, 1, 10000)) 47 | } 48 | 49 | // RandomizedGenState generates a random GenesisState for farming. 50 | func RandomizedGenState(simState *module.SimulationState) { 51 | var privatePlanCreationFee sdk.Coins 52 | simState.AppParams.GetOrGenerate( 53 | simState.Cdc, PrivatePlanCreationFee, &privatePlanCreationFee, simState.Rand, 54 | func(r *rand.Rand) { privatePlanCreationFee = GenPrivatePlanCreationFee(r) }, 55 | ) 56 | 57 | var nextEpochDays uint32 58 | simState.AppParams.GetOrGenerate( 59 | simState.Cdc, NextEpochDays, &nextEpochDays, simState.Rand, 60 | func(r *rand.Rand) { nextEpochDays = GenNextEpochDays(r) }, 61 | ) 62 | 63 | var feeCollector string 64 | simState.AppParams.GetOrGenerate( 65 | simState.Cdc, FarmingFeeCollector, &feeCollector, simState.Rand, 66 | func(r *rand.Rand) { feeCollector = GenFarmingFeeCollector(r) }, 67 | ) 68 | 69 | var currentEpochDays uint32 70 | simState.AppParams.GetOrGenerate( 71 | simState.Cdc, CurrentEpochDays, ¤tEpochDays, simState.Rand, 72 | func(r *rand.Rand) { currentEpochDays = GenCurrentEpochDays(r) }, 73 | ) 74 | 75 | var maxNumPrivatePlans uint32 76 | simState.AppParams.GetOrGenerate( 77 | simState.Cdc, MaxNumPrivatePlans, &maxNumPrivatePlans, simState.Rand, 78 | func(r *rand.Rand) { maxNumPrivatePlans = GenMaxNumPrivatePlans(r) }, 79 | ) 80 | 81 | farmingGenesis := types.GenesisState{ 82 | Params: types.Params{ 83 | PrivatePlanCreationFee: privatePlanCreationFee, 84 | NextEpochDays: nextEpochDays, 85 | FarmingFeeCollector: feeCollector, 86 | MaxNumPrivatePlans: maxNumPrivatePlans, 87 | }, 88 | CurrentEpochDays: currentEpochDays, 89 | } 90 | simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&farmingGenesis) 91 | } 92 | -------------------------------------------------------------------------------- /x/farming/simulation/proposals_test.go: -------------------------------------------------------------------------------- 1 | package simulation_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | 11 | "github.com/tendermint/farming/app/params" 12 | "github.com/tendermint/farming/x/farming/simulation" 13 | "github.com/tendermint/farming/x/farming/types" 14 | ) 15 | 16 | func TestProposalContents(t *testing.T) { 17 | app, ctx := createTestApp(false) 18 | 19 | // initialize parameters 20 | s := rand.NewSource(1) 21 | r := rand.New(s) 22 | 23 | accounts := getTestingAccounts(t, r, app, ctx, 1) 24 | 25 | // execute ProposalContents function 26 | weightedProposalContent := simulation.ProposalContents(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) 27 | require.Len(t, weightedProposalContent, 4) 28 | 29 | w0 := weightedProposalContent[0] 30 | w1 := weightedProposalContent[1] 31 | w2 := weightedProposalContent[2] 32 | 33 | // tests w0 interface: 34 | require.Equal(t, simulation.OpWeightSimulateAddPublicPlanProposal, w0.AppParamsKey()) 35 | require.Equal(t, params.DefaultWeightAddPublicPlanProposal, w0.DefaultWeight()) 36 | 37 | // tests w1 interface: 38 | require.Equal(t, simulation.OpWeightSimulateUpdatePublicPlanProposal, w1.AppParamsKey()) 39 | require.Equal(t, params.DefaultWeightUpdatePublicPlanProposal, w1.DefaultWeight()) 40 | 41 | // tests w2 interface: 42 | require.Equal(t, simulation.OpWeightSimulateDeletePublicPlanProposal, w2.AppParamsKey()) 43 | require.Equal(t, params.DefaultWeightDeletePublicPlanProposal, w2.DefaultWeight()) 44 | 45 | content0 := w0.ContentSimulatorFn()(r, ctx, accounts) 46 | require.Equal(t, "eOcbWwNbeH", content0.GetTitle()) 47 | require.Equal(t, "AjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjOygZsTxPjfweXhSUkMhPjMaxKlMIJMO", content0.GetDescription()) 48 | require.Equal(t, "farming", content0.ProposalRoute()) 49 | require.Equal(t, "PublicPlan", content0.ProposalType()) 50 | 51 | // setup public fixed amount plan 52 | msgPlan := &types.MsgCreateFixedAmountPlan{ 53 | Name: "simulation", 54 | Creator: accounts[0].Address.String(), 55 | StakingCoinWeights: sdk.NewDecCoins( 56 | sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% 57 | ), 58 | StartTime: types.ParseTime("2021-08-01T00:00:00Z"), 59 | EndTime: types.ParseTime("2021-08-31T00:00:00Z"), 60 | EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), 61 | } 62 | 63 | _, err := app.FarmingKeeper.CreateFixedAmountPlan( 64 | ctx, 65 | msgPlan, 66 | accounts[0].Address, 67 | accounts[0].Address, 68 | types.PlanTypePublic, 69 | ) 70 | require.NoError(t, err) 71 | 72 | content1 := w1.ContentSimulatorFn()(r, ctx, accounts) 73 | require.Equal(t, "OoMioXHRuF", content1.GetTitle()) 74 | require.Equal(t, "REqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvTyhXKLyhUScOXvYthRXpPfKwMhptXaxIxgqBoUqzrWbaoLTVpQoottZ", content1.GetDescription()) 75 | require.Equal(t, "farming", content1.ProposalRoute()) 76 | require.Equal(t, "PublicPlan", content1.ProposalType()) 77 | 78 | content2 := w2.ContentSimulatorFn()(r, ctx, accounts) 79 | require.Equal(t, "wQMUgFFSKt", content2.GetTitle()) 80 | require.Equal(t, "MwMANGoQwFnCqFrUGMCRZUGJKTZIGPyldsifauoMnJPLTcDHmilcmahlqOELaAUYDBuzsVywnDQfwRLGIWozYaOAilMBcObErwgT", content2.GetDescription()) 81 | require.Equal(t, "farming", content2.ProposalRoute()) 82 | require.Equal(t, "PublicPlan", content2.ProposalType()) 83 | } 84 | -------------------------------------------------------------------------------- /x/farming/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/tendermint/tendermint/libs/log" 8 | 9 | "github.com/cosmos/cosmos-sdk/codec" 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 12 | 13 | "github.com/tendermint/farming/x/farming/types" 14 | ) 15 | 16 | var ( 17 | enableAdvanceEpoch = "false" // Set this to "true" using build flags to enable AdvanceEpoch msg handling. 18 | enableRatioPlan = "false" // Set this to "true" using build flags to enable creation of RatioPlans. 19 | 20 | // EnableAdvanceEpoch indicates whether msgServer accepts MsgAdvanceEpoch or not. 21 | // Never set this to true in production mode. Doing that will expose serious attack vector. 22 | EnableAdvanceEpoch = false 23 | // EnableRatioPlan indicates whether msgServer and proposal handler accept 24 | // creation of RatioPlans. 25 | // Default is false, which means that RatioPlans can't be created through a 26 | // MsgCreateRatioPlan msg and a PublicPlanProposal. 27 | EnableRatioPlan = false 28 | ) 29 | 30 | func init() { 31 | var err error 32 | EnableAdvanceEpoch, err = strconv.ParseBool(enableAdvanceEpoch) 33 | if err != nil { 34 | panic(err) 35 | } 36 | EnableRatioPlan, err = strconv.ParseBool(enableRatioPlan) 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | 42 | // Keeper of the farming store 43 | type Keeper struct { 44 | storeKey sdk.StoreKey 45 | cdc codec.BinaryCodec 46 | paramSpace paramtypes.Subspace 47 | 48 | bankKeeper types.BankKeeper 49 | accountKeeper types.AccountKeeper 50 | 51 | blockedAddrs map[string]bool 52 | } 53 | 54 | // NewKeeper returns a farming keeper. It handles: 55 | // - creating new ModuleAccounts for each pool ReserveAccount 56 | // - sending to and from ModuleAccounts 57 | // - minting, burning PoolCoins 58 | func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, 59 | accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, 60 | blockedAddrs map[string]bool, 61 | ) Keeper { 62 | // ensure farming module account is set 63 | if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil { 64 | panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) 65 | } 66 | 67 | // set KeyTable if it has not already been set 68 | if !paramSpace.HasKeyTable() { 69 | paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) 70 | } 71 | 72 | return Keeper{ 73 | storeKey: key, 74 | cdc: cdc, 75 | paramSpace: paramSpace, 76 | accountKeeper: accountKeeper, 77 | bankKeeper: bankKeeper, 78 | blockedAddrs: blockedAddrs, 79 | } 80 | } 81 | 82 | // Logger returns a module-specific logger. 83 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 84 | return ctx.Logger().With("module", "x/"+types.ModuleName) 85 | } 86 | 87 | // GetParams returns the parameters for the farming module. 88 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { 89 | k.paramSpace.GetParamSet(ctx, ¶ms) 90 | return params 91 | } 92 | 93 | // SetParams sets the parameters for the farming module. 94 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { 95 | k.paramSpace.SetParamSet(ctx, ¶ms) 96 | } 97 | 98 | // GetCodec returns codec.Codec object used by the keeper> 99 | func (k Keeper) GetCodec() codec.BinaryCodec { return k.cdc } 100 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests / Code Coverage 2 | # Tests / Code Coverage workflow runs unit tests and uploads a code coverage report 3 | # This workflow is run on pushes to main & every Pull Requests where a .go, .mod, .sum have been changed 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | cleanup-runs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: rokroskar/workflow-run-cleanup-action@master 14 | env: 15 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 16 | if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop'" 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | go-arch: ["amd64", "arm64"] 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-go@v2.1.3 26 | with: 27 | go-version: 1.15 28 | - uses: technote-space/get-diff-action@v4 29 | id: git_diff 30 | with: 31 | PATTERNS: | 32 | **/**.go 33 | go.mod 34 | go.sum 35 | - name: Build 36 | run: GOARCH=${{ matrix.go-arch }} LEDGER_ENABLED=false make build 37 | 38 | test-coverage: 39 | runs-on: ubuntu-latest 40 | timeout-minutes: 15 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions/setup-go@v2.1.3 44 | with: 45 | go-version: 1.15 46 | - name: display go version 47 | run: go version 48 | - name: test & coverage report creation 49 | run: make test-cover 50 | - name: filter out DONTCOVER 51 | run: | 52 | excludelist="$(find ./ -type f -name '*.go' | xargs grep -l 'DONTCOVER')" 53 | excludelist+=" $(find ./ -type f -name '*.pb.go')" 54 | excludelist+=" $(find ./ -type f -name '*.pb.gw.go')" 55 | excludelist+=" $(find ./ -type f -path './tests/mocks/*.go')" 56 | for filename in ${excludelist}; do 57 | filename=$(echo $filename | sed 's/^./github.com\/tendermint\/farming/g') 58 | echo "Excluding ${filename} from coverage report..." 59 | sed -i "/$(echo $filename | sed 's/\//\\\//g')/d" coverage.txt 60 | done 61 | - uses: codecov/codecov-action@v1.0.14 62 | with: 63 | file: ./coverage.txt 64 | 65 | test-race: 66 | runs-on: ubuntu-latest 67 | timeout-minutes: 15 68 | steps: 69 | - uses: actions/checkout@v2 70 | - uses: actions/setup-go@v2.1.3 71 | with: 72 | go-version: 1.15 73 | - name: display go version 74 | run: go version 75 | - name: test & coverage report creation 76 | run: make test-race 77 | - name: filter out DONTCOVER 78 | run: | 79 | excludelist="$(find ./ -type f -name '*.go' | xargs grep -l 'DONTCOVER')" 80 | excludelist+=" $(find ./ -type f -name '*.pb.go')" 81 | excludelist+=" $(find ./ -type f -name '*.pb.gw.go')" 82 | excludelist+=" $(find ./ -type f -path './tests/mocks/*.go')" 83 | for filename in ${excludelist}; do 84 | filename=$(echo $filename | sed 's/^./github.com\/tendermint\/farming/g') 85 | echo "Excluding ${filename} from coverage report..." 86 | sed -i "/$(echo $filename | sed 's/\//\\\//g')/d" coverage.txt 87 | done 88 | - uses: codecov/codecov-action@v1.0.14 89 | with: 90 | file: ./coverage.txt -------------------------------------------------------------------------------- /x/farming/keeper/epoch_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/tendermint/farming/x/farming" 8 | "github.com/tendermint/farming/x/farming/types" 9 | 10 | _ "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | func (suite *KeeperTestSuite) TestLastEpochTime() { 14 | _, found := suite.keeper.GetLastEpochTime(suite.ctx) 15 | suite.Require().False(found) 16 | 17 | t := types.ParseTime("2021-07-23T05:01:02Z") 18 | suite.keeper.SetLastEpochTime(suite.ctx, t) 19 | 20 | t2, found := suite.keeper.GetLastEpochTime(suite.ctx) 21 | suite.Require().True(found) 22 | suite.Require().Equal(t, t2) 23 | } 24 | 25 | func (suite *KeeperTestSuite) TestFirstEpoch() { 26 | // The first epoch may run very quickly depending on when 27 | // the farming module was activated, 28 | // meaning that (block time) - (last epoch time) may be smaller 29 | // than the current epoch_days for the first epoch. 30 | 31 | suite.Require().Equal(uint32(1), suite.keeper.GetCurrentEpochDays(suite.ctx)) 32 | 33 | suite.ctx = suite.ctx.WithBlockTime(types.ParseTime("2021-08-11T23:59:59Z")) 34 | farming.EndBlocker(suite.ctx, suite.keeper) 35 | lastEpochTime, found := suite.keeper.GetLastEpochTime(suite.ctx) 36 | suite.Require().True(found) 37 | 38 | suite.ctx = suite.ctx.WithBlockTime(types.ParseTime("2021-08-12T00:00:00Z")) 39 | farming.EndBlocker(suite.ctx, suite.keeper) 40 | t, _ := suite.keeper.GetLastEpochTime(suite.ctx) 41 | suite.Require().True(t.After(lastEpochTime)) // Indicating that the epoch ended. 42 | } 43 | 44 | func (suite *KeeperTestSuite) TestEpochDays() { 45 | for _, nextEpochDays := range []uint32{1, 2, 3} { 46 | suite.Run(fmt.Sprintf("next epoch days = %d", nextEpochDays), func() { 47 | suite.SetupTest() 48 | 49 | params := suite.keeper.GetParams(suite.ctx) 50 | params.NextEpochDays = nextEpochDays 51 | suite.keeper.SetParams(suite.ctx, params) 52 | 53 | t := types.ParseTime("2021-08-11T00:00:00Z") 54 | suite.ctx = suite.ctx.WithBlockTime(t) 55 | farming.EndBlocker(suite.ctx, suite.keeper) 56 | 57 | lastEpochTime, _ := suite.keeper.GetLastEpochTime(suite.ctx) 58 | currentEpochDays := suite.keeper.GetCurrentEpochDays(suite.ctx) 59 | 60 | for i := 0; i < 10000; i++ { 61 | t = t.Add(5 * time.Minute) 62 | suite.ctx = suite.ctx.WithBlockTime(t) 63 | farming.EndBlocker(suite.ctx, suite.keeper) 64 | 65 | t2, _ := suite.keeper.GetLastEpochTime(suite.ctx) 66 | if t2.After(lastEpochTime) { 67 | suite.Require().GreaterOrEqual(t2.Sub(lastEpochTime).Hours(), float64(currentEpochDays*24)) 68 | lastEpochTime = t2 69 | } 70 | } 71 | }) 72 | } 73 | } 74 | func (suite *KeeperTestSuite) TestDelayedBlockTime() { 75 | // Entire network can be down for several days, 76 | // and the epoch should be advanced after the downtime. 77 | suite.keeper.SetLastEpochTime(suite.ctx, types.ParseTime("2021-09-23T00:00:05Z")) 78 | 79 | t := types.ParseTime("2021-10-03T00:00:04Z") 80 | suite.ctx = suite.ctx.WithBlockTime(t) 81 | farming.EndBlocker(suite.ctx, suite.keeper) 82 | 83 | lastEpochTime, found := suite.keeper.GetLastEpochTime(suite.ctx) 84 | suite.Require().True(found) 85 | suite.Require().Equal(t, lastEpochTime) 86 | } 87 | 88 | func (suite *KeeperTestSuite) TestCurrentEpochDays() { 89 | currentEpochDays := suite.keeper.GetCurrentEpochDays(suite.ctx) 90 | suite.Require().Equal(uint32(1), currentEpochDays) 91 | 92 | nextEpochDays := uint32(3) 93 | suite.keeper.SetCurrentEpochDays(suite.ctx, nextEpochDays) 94 | 95 | currentEpochDays = suite.keeper.GetCurrentEpochDays(suite.ctx) 96 | suite.Require().Equal(uint32(3), currentEpochDays) 97 | } 98 | -------------------------------------------------------------------------------- /x/farming/client/cli/utils_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/cosmos/cosmos-sdk/testutil" 10 | 11 | "github.com/tendermint/farming/app/params" 12 | "github.com/tendermint/farming/x/farming/client/cli" 13 | ) 14 | 15 | func TestParsePrivateFixedPlan(t *testing.T) { 16 | okJSON := testutil.WriteToNewTempFile(t, ` 17 | { 18 | "name": "This plan intends to provide incentives for Cosmonauts!", 19 | "staking_coin_weights": [ 20 | { 21 | "denom": "PoolCoinDenom", 22 | "amount": "1.000000000000000000" 23 | } 24 | ], 25 | "start_time": "2021-07-15T08:41:21Z", 26 | "end_time": "2022-07-16T08:41:21Z", 27 | "epoch_amount": [ 28 | { 29 | "denom": "uatom", 30 | "amount": "1" 31 | } 32 | ] 33 | } 34 | `) 35 | 36 | plan, err := cli.ParsePrivateFixedPlan(okJSON.Name()) 37 | require.NoError(t, err) 38 | require.NotEmpty(t, plan.String()) 39 | 40 | require.Equal(t, "This plan intends to provide incentives for Cosmonauts!", plan.Name) 41 | require.Equal(t, "1.000000000000000000PoolCoinDenom", plan.StakingCoinWeights.String()) 42 | require.Equal(t, "2021-07-15T08:41:21Z", plan.StartTime.Format(time.RFC3339)) 43 | require.Equal(t, "2022-07-16T08:41:21Z", plan.EndTime.Format(time.RFC3339)) 44 | require.Equal(t, "1uatom", plan.EpochAmount.String()) 45 | } 46 | 47 | func TestParsePrivateRatioPlan(t *testing.T) { 48 | okJSON := testutil.WriteToNewTempFile(t, ` 49 | { 50 | "name": "This plan intends to provide incentives for Cosmonauts!", 51 | "staking_coin_weights": [ 52 | { 53 | "denom": "PoolCoinDenom", 54 | "amount": "1.000000000000000000" 55 | } 56 | ], 57 | "start_time": "2021-07-15T08:41:21Z", 58 | "end_time": "2022-07-16T08:41:21Z", 59 | "epoch_ratio": "1.000000000000000000" 60 | } 61 | `) 62 | 63 | plan, err := cli.ParsePrivateRatioPlan(okJSON.Name()) 64 | require.NoError(t, err) 65 | require.NotEmpty(t, plan.String()) 66 | 67 | require.Equal(t, "This plan intends to provide incentives for Cosmonauts!", plan.Name) 68 | require.Equal(t, "1.000000000000000000PoolCoinDenom", plan.StakingCoinWeights.String()) 69 | require.Equal(t, "2021-07-15T08:41:21Z", plan.StartTime.Format(time.RFC3339)) 70 | require.Equal(t, "2022-07-16T08:41:21Z", plan.EndTime.Format(time.RFC3339)) 71 | require.Equal(t, "1.000000000000000000", plan.EpochRatio.String()) 72 | } 73 | 74 | func TestParsePublicPlanProposal(t *testing.T) { 75 | encodingConfig := params.MakeTestEncodingConfig() 76 | 77 | okJSON := testutil.WriteToNewTempFile(t, ` 78 | { 79 | "title": "Public Farming Plan", 80 | "description": "Are you ready to farm?", 81 | "add_plan_requests": [ 82 | { 83 | "name": "First Public Farming Plan", 84 | "farming_pool_address": "cosmos1mzgucqnfr2l8cj5apvdpllhzt4zeuh2cshz5xu", 85 | "termination_address": "cosmos1mzgucqnfr2l8cj5apvdpllhzt4zeuh2cshz5xu", 86 | "staking_coin_weights": [ 87 | { 88 | "denom": "PoolCoinDenom", 89 | "amount": "1.000000000000000000" 90 | } 91 | ], 92 | "start_time": "2021-07-15T08:41:21Z", 93 | "end_time": "2022-07-16T08:41:21Z", 94 | "epoch_amount": [ 95 | { 96 | "denom": "uatom", 97 | "amount": "1" 98 | } 99 | ] 100 | } 101 | ] 102 | } 103 | `) 104 | 105 | proposal, err := cli.ParsePublicPlanProposal(encodingConfig.Marshaler, okJSON.Name()) 106 | require.NoError(t, err) 107 | 108 | require.Equal(t, "Public Farming Plan", proposal.Title) 109 | require.Equal(t, "Are you ready to farm?", proposal.Description) 110 | } 111 | -------------------------------------------------------------------------------- /.github/workflows/sims.yml: -------------------------------------------------------------------------------- 1 | name: Sims 2 | # Sims workflow runs multiple types of simulations (nondeterminism, import-export, after-import) 3 | # This workflow will run on all Pull Requests, if a .go, .mod or .sum file have been changed 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - develop 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | if: "!contains(github.event.head_commit.message, 'skip-sims')" 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-go@v2.1.3 18 | with: 19 | go-version: 1.16 20 | - name: Display go version 21 | run: go version 22 | - run: make build 23 | 24 | install-runsim: 25 | runs-on: ubuntu-latest 26 | needs: build 27 | steps: 28 | - uses: actions/setup-go@v2.1.3 29 | with: 30 | go-version: 1.16 31 | - name: Display go version 32 | run: go version 33 | - name: Install runsim 34 | run: export GO111MODULE="on" && go get github.com/cosmos/tools/cmd/runsim@v1.0.0 35 | - uses: actions/cache@v2.1.6 36 | with: 37 | path: ~/go/bin 38 | key: ${{ runner.os }}-go-runsim-binary 39 | 40 | test-sim-nondeterminism: 41 | runs-on: ubuntu-latest 42 | needs: [build, install-runsim] 43 | steps: 44 | - uses: actions/checkout@v2 45 | - uses: actions/setup-go@v2.1.3 46 | with: 47 | go-version: 1.16 48 | - name: Display go version 49 | run: go version 50 | - uses: technote-space/get-diff-action@v4 51 | with: 52 | PATTERNS: | 53 | **/**.go 54 | go.mod 55 | go.sum 56 | - uses: actions/cache@v2.1.6 57 | with: 58 | path: ~/go/bin 59 | key: ${{ runner.os }}-go-runsim-binary 60 | if: env.GIT_DIFF 61 | - name: test-sim-nondeterminism 62 | run: | 63 | make test-sim-nondeterminism 64 | if: env.GIT_DIFF 65 | 66 | test-sim-import-export: 67 | runs-on: ubuntu-latest 68 | needs: [build, install-runsim] 69 | steps: 70 | - uses: actions/checkout@v2 71 | - uses: actions/setup-go@v2.1.3 72 | with: 73 | go-version: 1.16 74 | - name: Display go version 75 | run: go version 76 | - uses: technote-space/get-diff-action@v4 77 | with: 78 | SUFFIX_FILTER: | 79 | **/**.go 80 | go.mod 81 | go.sum 82 | SET_ENV_NAME_INSERTIONS: 1 83 | SET_ENV_NAME_LINES: 1 84 | - uses: actions/cache@v2.1.6 85 | with: 86 | path: ~/go/bin 87 | key: ${{ runner.os }}-go-runsim-binary 88 | if: env.GIT_DIFF 89 | - name: test-sim-import-export 90 | run: | 91 | make test-sim-import-export 92 | if: env.GIT_DIFF 93 | 94 | test-sim-after-import: 95 | runs-on: ubuntu-latest 96 | needs: [build, install-runsim] 97 | steps: 98 | - uses: actions/checkout@v2 99 | - uses: actions/setup-go@v2.1.3 100 | with: 101 | go-version: 1.16 102 | - name: Display go version 103 | run: go version 104 | - uses: technote-space/get-diff-action@v4 105 | with: 106 | SUFFIX_FILTER: | 107 | **/**.go 108 | go.mod 109 | go.sum 110 | SET_ENV_NAME_INSERTIONS: 1 111 | SET_ENV_NAME_LINES: 1 112 | - uses: actions/cache@v2.1.6 113 | with: 114 | path: ~/go/bin 115 | key: ${{ runner.os }}-go-runsim-binary 116 | if: env.GIT_DIFF 117 | - name: test-sim-after-import 118 | run: | 119 | make test-sim-after-import 120 | if: env.GIT_DIFF 121 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Proto 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: true 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 120 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Delimiters: 95 | - pb 96 | Language: TextProto 97 | BasedOnStyle: google 98 | ReflowComments: true 99 | SortIncludes: true 100 | SortUsingDeclarations: true 101 | SpaceAfterCStyleCast: false 102 | SpaceAfterTemplateKeyword: true 103 | SpaceBeforeAssignmentOperators: true 104 | SpaceBeforeParens: ControlStatements 105 | SpaceInEmptyParentheses: false 106 | SpacesBeforeTrailingComments: 1 107 | SpacesInAngles: false 108 | SpacesInContainerLiterals: false 109 | SpacesInCStyleCastParentheses: false 110 | SpacesInParentheses: false 111 | SpacesInSquareBrackets: false 112 | Standard: Cpp11 113 | TabWidth: 8 114 | UseTab: Never 115 | ... 116 | 117 | -------------------------------------------------------------------------------- /x/farming/spec/01_concepts.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Concepts 4 | 5 | ## Farming Module 6 | 7 | The `x/farming` Cosmos SDK module implements farming functionality that keeps track of staking and provides farming rewards to farmers. A primary use case of this module is to provide incentives for liquidity pool investors for their pool participation. 8 | 9 | ## Plans 10 | 11 | There are two types of farming plans in the `farming` module: 12 | 13 | ### Public Farming Plan 14 | 15 | A public farming plan can be created only through governance proposal. 16 | 17 | - The proposal must be first agreed and passed before a public farming plan can be created. 18 | - A creation fee is not required. 19 | 20 | ### Private Farming Plan 21 | 22 | A private farming plan can be created with any account. 23 | 24 | - The address of the plan creator account is used as the `TerminationAddress`. When the plan ends after its end time, the balance of the farming pool address is transferred to the termination address. 25 | - To prevent spamming attacks, the `PlanCreationFee` fee must be paid on plan creation. 26 | - Internally, the private plan's farming pool address is derived from the following derivation rule of `address.Module(ModuleName, []byte("PrivatePlan|{planId}|{planName}"))` and it is assigned to the plan. 27 | - After creation, need to query the plan and send the amount of coins to the farming pool address so that the plan distributes as intended. 28 | 29 | ## Distribution Methods 30 | 31 | There are two types of reward distribution methods in the `farming` module: 32 | 33 | ### Fixed Amount Plan 34 | 35 | A `FixedAmountPlan` distributes a fixed amount of coins to farmers for every epoch day. 36 | 37 | When the plan creator's `FarmingPoolAddress` is depleted, then there are no more coins to distribute until more coins are added to the account. 38 | 39 | ### Ratio Plan 40 | 41 | A `RatioPlan` distributes coins to farmers by ratio distribution for every epoch day. 42 | 43 | If the plan creator's `FarmingPoolAddress` is depleted, then there are no more coins to distribute until more coins are added to the account. 44 | 45 | ## Accumulated Reward Calculation 46 | 47 | In the farming module, farming rewards are calculated per epoch based on the distribution plan. 48 | 49 | To calculate the rewards for a single farmer, take the total rewards for the epoch before the staking started, minus the current total rewards. 50 | 51 | The farming module takes references from [F1 Fee Distribution](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/fee_distribution/f1_fee_distr.pdf) that is used in the Cosmos SDK [x/distribution](https://github.com/cosmos/cosmos-sdk/blob/master/x/distribution/spec/01_concepts.md) module. 52 | 53 | ### Accumulated Unit Reward 54 | 55 | `HistoricalRewards` represents accumulated rewards for each staking coin with amount 1. 56 | 57 | ### Base Algorithm 58 | 59 | `HistoricalRewards` for each staking coin for every epoch can be calculated as the following algorithm: 60 | 61 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;\sum_{i=0}^{now}\frac{TR_i}{TS_i}) 62 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;i) : each epoch 63 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;now) : `CurrentEpoch` 64 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;TS_i) : total staking amount of the staking coin for epoch i 65 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;TR_i) : total reward amount of the staking coin for epoch i 66 | 67 | Accumulated rewards from any staking position can be calculated from `HistoricalRewards` and the staking amount of the position as the following algorithm: 68 | 69 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;x*\(\sum_{i=0}^{now}\frac{TR_i}{TS_i}-\sum_{i=0}^{start}\frac{TR_i}{TS_i}\)) 70 | - assuming constant staking amount for the staking epochs 71 | - ![](https://latex.codecogs.com/svg.latex?\Large&space;x) : staking amount for the staking period -------------------------------------------------------------------------------- /x/farming/client/testutil/cli_helpers.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | dbm "github.com/tendermint/tm-db" 7 | 8 | "github.com/cosmos/cosmos-sdk/baseapp" 9 | "github.com/cosmos/cosmos-sdk/client" 10 | "github.com/cosmos/cosmos-sdk/client/flags" 11 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 12 | "github.com/cosmos/cosmos-sdk/simapp" 13 | "github.com/cosmos/cosmos-sdk/simapp/params" 14 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 15 | "github.com/cosmos/cosmos-sdk/testutil" 16 | clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" 17 | "github.com/cosmos/cosmos-sdk/testutil/network" 18 | sdk "github.com/cosmos/cosmos-sdk/types" 19 | bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/cli" 20 | 21 | farmingapp "github.com/tendermint/farming/app" 22 | farmingcli "github.com/tendermint/farming/x/farming/client/cli" 23 | ) 24 | 25 | // NewConfig returns config that defines the necessary testing requirements 26 | // used to bootstrap and start an in-process local testing network. 27 | func NewConfig(dbm *dbm.MemDB) network.Config { 28 | encCfg := simapp.MakeTestEncodingConfig() 29 | 30 | cfg := network.DefaultConfig() 31 | cfg.AppConstructor = NewAppConstructor(encCfg, dbm) // the ABCI application constructor 32 | cfg.GenesisState = farmingapp.ModuleBasics.DefaultGenesis(cfg.Codec) // farming genesis state to provide 33 | return cfg 34 | } 35 | 36 | // NewAppConstructor returns a new network AppConstructor. 37 | func NewAppConstructor(encodingCfg params.EncodingConfig, db *dbm.MemDB) network.AppConstructor { 38 | return func(val network.Validator) servertypes.Application { 39 | return farmingapp.NewFarmingApp( 40 | val.Ctx.Logger, db, nil, true, make(map[int64]bool), val.Ctx.Config.RootDir, 0, 41 | farmingapp.MakeEncodingConfig(), 42 | simapp.EmptyAppOptions{}, 43 | baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)), 44 | baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices), 45 | ) 46 | } 47 | } 48 | 49 | var commonArgs = []string{ 50 | fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), 51 | fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), 52 | fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), 53 | } 54 | 55 | // MsgCreateFixedAmountPlanExec creates a transaction for creating a private fixed amount plan. 56 | func MsgCreateFixedAmountPlanExec(clientCtx client.Context, from string, file string, 57 | extraArgs ...string) (testutil.BufferWriter, error) { 58 | 59 | args := append([]string{ 60 | file, 61 | fmt.Sprintf("--%s=%s", flags.FlagFrom, from), 62 | }, commonArgs...) 63 | 64 | args = append(args, commonArgs...) 65 | 66 | return clitestutil.ExecTestCLICmd(clientCtx, farmingcli.NewCreateFixedAmountPlanCmd(), args) 67 | } 68 | 69 | // MsgStakeExec creates a transaction for staking coin. 70 | func MsgStakeExec(clientCtx client.Context, from string, stakingCoins string, 71 | extraArgs ...string) (testutil.BufferWriter, error) { 72 | 73 | args := append([]string{ 74 | stakingCoins, 75 | fmt.Sprintf("--%s=%s", flags.FlagFrom, from), 76 | }, commonArgs...) 77 | 78 | args = append(args, commonArgs...) 79 | 80 | return clitestutil.ExecTestCLICmd(clientCtx, farmingcli.NewStakeCmd(), args) 81 | } 82 | 83 | // MsgAdvanceEpochExec creates a transaction to advance epoch by 1. 84 | func MsgAdvanceEpochExec(clientCtx client.Context, from string, 85 | extraAtgs ...string) (testutil.BufferWriter, error) { 86 | 87 | args := append([]string{ 88 | fmt.Sprintf("--%s=%s", flags.FlagFrom, from), 89 | }, commonArgs...) 90 | 91 | args = append(args, commonArgs...) 92 | 93 | return clitestutil.ExecTestCLICmd(clientCtx, farmingcli.NewAdvanceEpochCmd(), args) 94 | } 95 | 96 | // MsgSendExec creates a transaction to transfer coins. 97 | func MsgSendExec(clientCtx client.Context, from string, to string, amount string, 98 | extraAtgs ...string) (testutil.BufferWriter, error) { 99 | 100 | args := append([]string{ 101 | from, 102 | to, 103 | amount, 104 | }, commonArgs...) 105 | 106 | args = append(args, commonArgs...) 107 | 108 | return clitestutil.ExecTestCLICmd(clientCtx, bankcli.NewSendTxCmd(), args) 109 | } 110 | -------------------------------------------------------------------------------- /x/farming/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | // DONTCOVER 4 | 5 | // Although written in msg_server_test.go, it is approached at the keeper level rather than at the msgServer level 6 | // so is not included in the coverage. 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | 14 | "github.com/tendermint/farming/x/farming/types" 15 | ) 16 | 17 | type msgServer struct { 18 | Keeper 19 | } 20 | 21 | // NewMsgServerImpl returns an implementation of the farming MsgServer interface 22 | // for the provided Keeper. 23 | func NewMsgServerImpl(keeper Keeper) types.MsgServer { 24 | return &msgServer{Keeper: keeper} 25 | } 26 | 27 | var _ types.MsgServer = msgServer{} 28 | 29 | // CreateFixedAmountPlan defines a method for creating fixed amount farming plan. 30 | func (k msgServer) CreateFixedAmountPlan(goCtx context.Context, msg *types.MsgCreateFixedAmountPlan) (*types.MsgCreateFixedAmountPlanResponse, error) { 31 | ctx := sdk.UnwrapSDKContext(goCtx) 32 | poolAcc, err := k.DerivePrivatePlanFarmingPoolAcc(ctx, msg.Name) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | if _, err := k.Keeper.CreateFixedAmountPlan(ctx, msg, poolAcc, msg.GetCreator(), types.PlanTypePrivate); err != nil { 38 | return nil, err 39 | } 40 | 41 | return &types.MsgCreateFixedAmountPlanResponse{}, nil 42 | } 43 | 44 | // CreateRatioPlan defines a method for creating ratio farming plan. 45 | func (k msgServer) CreateRatioPlan(goCtx context.Context, msg *types.MsgCreateRatioPlan) (*types.MsgCreateRatioPlanResponse, error) { 46 | ctx := sdk.UnwrapSDKContext(goCtx) 47 | 48 | if !EnableRatioPlan { 49 | return nil, types.ErrRatioPlanDisabled 50 | } 51 | 52 | poolAcc, err := k.DerivePrivatePlanFarmingPoolAcc(ctx, msg.Name) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if _, err := k.Keeper.CreateRatioPlan(ctx, msg, poolAcc, msg.GetCreator(), types.PlanTypePrivate); err != nil { 58 | return nil, err 59 | } 60 | 61 | plans := k.GetPlans(ctx) 62 | if err := types.ValidateTotalEpochRatio(plans); err != nil { 63 | return nil, err 64 | } 65 | 66 | return &types.MsgCreateRatioPlanResponse{}, nil 67 | } 68 | 69 | // Stake defines a method for staking coins to the farming plan. 70 | func (k msgServer) Stake(goCtx context.Context, msg *types.MsgStake) (*types.MsgStakeResponse, error) { 71 | ctx := sdk.UnwrapSDKContext(goCtx) 72 | 73 | if err := k.Keeper.Stake(ctx, msg.GetFarmer(), msg.StakingCoins); err != nil { 74 | return nil, err 75 | } 76 | 77 | return &types.MsgStakeResponse{}, nil 78 | } 79 | 80 | // Unstake defines a method for unstaking coins from the farming plan. 81 | func (k msgServer) Unstake(goCtx context.Context, msg *types.MsgUnstake) (*types.MsgUnstakeResponse, error) { 82 | ctx := sdk.UnwrapSDKContext(goCtx) 83 | 84 | if err := k.Keeper.Unstake(ctx, msg.GetFarmer(), msg.UnstakingCoins); err != nil { 85 | return nil, err 86 | } 87 | 88 | return &types.MsgUnstakeResponse{}, nil 89 | } 90 | 91 | // Harvest defines a method for claiming farming rewards from the farming plan. 92 | func (k msgServer) Harvest(goCtx context.Context, msg *types.MsgHarvest) (*types.MsgHarvestResponse, error) { 93 | ctx := sdk.UnwrapSDKContext(goCtx) 94 | 95 | if err := k.Keeper.Harvest(ctx, msg.GetFarmer(), msg.StakingCoinDenoms); err != nil { 96 | return nil, err 97 | } 98 | 99 | return &types.MsgHarvestResponse{}, nil 100 | } 101 | 102 | func (k msgServer) RemovePlan(goCtx context.Context, msg *types.MsgRemovePlan) (*types.MsgRemovePlanResponse, error) { 103 | ctx := sdk.UnwrapSDKContext(goCtx) 104 | 105 | if err := k.Keeper.RemovePlan(ctx, msg.GetCreator(), msg.PlanId); err != nil { 106 | return nil, err 107 | } 108 | 109 | return &types.MsgRemovePlanResponse{}, nil 110 | } 111 | 112 | // AdvanceEpoch defines a method for advancing epoch by one, just for testing purpose 113 | // and shouldn't be used in real world. 114 | func (k msgServer) AdvanceEpoch(goCtx context.Context, msg *types.MsgAdvanceEpoch) (*types.MsgAdvanceEpochResponse, error) { 115 | ctx := sdk.UnwrapSDKContext(goCtx) 116 | 117 | if EnableAdvanceEpoch { 118 | if err := k.Keeper.AdvanceEpoch(ctx); err != nil { 119 | return nil, err 120 | } 121 | } else { 122 | return nil, fmt.Errorf("AdvanceEpoch is disabled") 123 | } 124 | 125 | return &types.MsgAdvanceEpochResponse{}, nil 126 | } 127 | -------------------------------------------------------------------------------- /docs/Reference/proposals/01_proposal.md: -------------------------------------------------------------------------------- 1 | # Signal Proposal 2 | 3 | Adopting the `Budget` and `Farming` Modules on Cosmos Hub 4 | 5 | By voting yes to this signal proposal, the voter agrees to adopt the Budget Module and Farming Module on Cosmos Hub to allow an incentivization mechanism for userbase growth on Cosmos Hub. The Tendermint team is currently building the two suggested modules, and when this signal proposal passes, the Budget Module and Farming Module will be included in a future Gaia upgrade when both modules are ready to be deployed. 6 | 7 | ## 1. Introduction 8 | 9 | ### 1.1 Modern Blockchain Incentivization Scheme: Farming 10 | 11 | Lots of modern blockchain ecosystems and decentralized applications incentivize their platform users by distributing coins based on user activities to bootstrap the growth of the userbase. Generally, the incentivization methodology is called "Farming." The source of the farming is various, but the most popular way is to utilize the native coins for current and future platform users. 12 | 13 | ### 1.2. Cosmos Hub Context: Budget & Farming 14 | 15 | With pipelines of new features included on Cosmos Hub, including IBC, bridges, and Gravity DEX, Cosmos Hub needs to incentivize not only dPoS delegators but also platform users to accelerate user adoption. So we need to adopt two kinds of features as below: 16 | 17 | - Budget Module: To define and execute budget plans of ATOM inflation for multiple objectives 18 | 19 | - Farming Module: To define and distribute incentives for various utility users on Cosmos Hub 20 | 21 | ## 2. Features 22 | 23 | ### 2.1. Budget Module 24 | 25 | Budget Plan 26 | 27 | The Budget Module manages a list of budget plans which describe each proportional distribution of ATOM inflation to different destinations. The Budget Module distributes ATOM inflation according to the existing list of budget plans. 28 | 29 | Governance Process 30 | 31 | The list of budget plans can be added/removed/modified by a parameter governance proposal. 32 | 33 | ### 2.2. Farming Module 34 | 35 | Farming Plan 36 | 37 | A farming plan is a definition of reward distribution plan with two types: 38 | 39 | - Public Farming Plan 40 | 41 | - Creation: A public farming plan can be created only by the governance process 42 | 43 | - Farming Pool Address: The source of a public farming plan is an existing module account 44 | 45 | - Private Farming Plan 46 | 47 | - Creation: A private farming plan can be created by anyone, submitting a transaction 48 | 49 | - Farming Pool Address: A new farming pool address is assigned to the farming plan. Anyone can fund this farming pool by sending coins to this address. 50 | 51 | Reward Distribution 52 | 53 | A farming plan defines a list of staking coin weights which are used to calculate the proportional distribution of rewards to each farmer. From the total reward distribution, each staking coin gets the weight proportion defined in the weight list. Then, each farmer who staked this coin receives the amount of corresponding rewards based on their proportion of the staked coin amount from the total staked amount. 54 | 55 | Reward Harvest 56 | 57 | A farmer can harvest (withdraw) accumulated rewards anytime he/she wants. Rewards are calculated based on a predefined epoch, therefore farmers can harvest rewards accumulated until the last epoch. 58 | 59 | ### 2.3. Gravity DEX Liquidity Incentivization 60 | 61 | Staking Coins as Pool Coins 62 | 63 | Staking coins in a farming plan can be defined as a group of pool coins to distribute the farming plan rewards to pool coin holders. Because every liquidity provider on Gravity DEX gets pool coins as evidence of liquidity providing, this methodology naturally provides us the way to incentivize liquidity providing on Gravity DEX 64 | 65 | Governance Processes 66 | 67 | We need two kinds of governance processes to activate Gravity DEX liquidity incentivization 68 | 69 | - Budget: A governance process to decide 70 | 71 | - percentage of ATOM inflation to be used for Gravity DEX liquidity incentivization 72 | 73 | - time period of the budget plan created by this governance process 74 | 75 | - Farming: A governance process to decide 76 | 77 | - list and weights of staking coins (pool coins) to be incentivized 78 | 79 | - time period of the farming plan created by this governance process 80 | 81 | ## 3. Detail Spec 82 | 83 | Detail description of the spec can be found below: 84 | 85 | - Budget Module: https://github.com/tendermint/budget/tree/main/x/budget/spec 86 | 87 | - Farming Module: https://github.com/tendermint/farming/tree/main/x/farming/spec -------------------------------------------------------------------------------- /x/farming/types/utils_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/tendermint/farming/x/farming/types" 10 | ) 11 | 12 | func TestParseTime(t *testing.T) { 13 | normalCase := "9999-12-31T00:00:00Z" 14 | normalRes, err := time.Parse(time.RFC3339, normalCase) 15 | require.NoError(t, err) 16 | errorCase := "9999-12-31T00:00:00_ErrorCase" 17 | _, err = time.Parse(time.RFC3339, errorCase) 18 | require.PanicsWithError(t, err.Error(), func() { types.ParseTime(errorCase) }) 19 | require.Equal(t, normalRes, types.ParseTime(normalCase)) 20 | } 21 | 22 | func TestDateRangeIncludes(t *testing.T) { 23 | testCases := []struct { 24 | name string 25 | expectedResult bool 26 | targeTime time.Time 27 | startTime time.Time 28 | endTime time.Time 29 | }{ 30 | { 31 | "not included, before started", 32 | false, 33 | types.ParseTime("2021-12-02T00:00:00Z"), 34 | types.ParseTime("2021-12-02T00:00:01Z"), 35 | types.ParseTime("2021-12-03T00:00:00Z"), 36 | }, 37 | { 38 | "not included, after ended", 39 | false, 40 | types.ParseTime("2021-12-03T00:00:01Z"), 41 | types.ParseTime("2021-12-02T00:00:00Z"), 42 | types.ParseTime("2021-12-03T00:00:00Z"), 43 | }, 44 | { 45 | "included on start time", 46 | true, 47 | types.ParseTime("2021-12-02T00:00:00Z"), 48 | types.ParseTime("2021-12-02T00:00:00Z"), 49 | types.ParseTime("2021-12-03T00:00:00Z"), 50 | }, 51 | { 52 | "not included on end time", 53 | false, 54 | types.ParseTime("2021-12-02T00:00:00Z"), 55 | types.ParseTime("2021-12-01T00:00:00Z"), 56 | types.ParseTime("2021-12-02T00:00:00Z"), 57 | }, 58 | { 59 | "not included on same start time and end time", 60 | false, 61 | types.ParseTime("2021-12-02T00:00:00Z"), 62 | types.ParseTime("2021-12-02T00:00:00Z"), 63 | types.ParseTime("2021-12-02T00:00:00Z"), 64 | }, 65 | } 66 | for _, tc := range testCases { 67 | t.Run(tc.name, func(t *testing.T) { 68 | require.Equal(t, tc.expectedResult, types.DateRangeIncludes(tc.startTime, tc.endTime, tc.targeTime)) 69 | }) 70 | } 71 | } 72 | 73 | func TestDeriveAddress(t *testing.T) { 74 | testCases := []struct { 75 | addressType types.AddressType 76 | moduleName string 77 | name string 78 | expectedAddress string 79 | }{ 80 | { 81 | types.ReserveAddressType, 82 | types.ModuleName, 83 | "StakingReserveAcc|uatom", 84 | "cosmos1qxs9gxctmd637l7ckpc99kw6ax6thgxx5kshpgzc8kup675xp9dsank7up", 85 | }, 86 | { 87 | types.ReserveAddressType, 88 | types.ModuleName, 89 | "StakingReserveAcc|stake", 90 | "cosmos1jn5vt4c3xg38ud89xjl8aumlf3akgdpllmt986w5tj9lureh65dsvk5z3t", 91 | }, 92 | { 93 | types.AddressType20Bytes, 94 | "", 95 | "fee_collector", 96 | "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta", 97 | }, 98 | { 99 | types.AddressType32Bytes, 100 | "farming", 101 | "GravityDEXFarmingBudget", 102 | "cosmos1228ryjucdpdv3t87rxle0ew76a56ulvnfst0hq0sscd3nafgjpqqkcxcky", 103 | }, 104 | { 105 | types.AddressType20Bytes, 106 | types.ModuleName, 107 | "", 108 | "cosmos1g8n25wpvvs38dec43jtt5a8w2td3nkmz6d2qfh", 109 | }, 110 | { 111 | types.AddressType20Bytes, 112 | types.ModuleName, 113 | "test1", 114 | "cosmos19jjdxeykth523wg4xetyf2gr07pykjn60egn0y", 115 | }, 116 | { 117 | types.AddressType32Bytes, 118 | types.ModuleName, 119 | "test1", 120 | "cosmos1tveg5at4u8tzulwrq4qq4gnxln729t8r72aphsx9euwsw0cmeq7qudxdv8", 121 | }, 122 | { 123 | types.AddressType32Bytes, 124 | "test2", 125 | "", 126 | "cosmos1v9ejakp386det8xftkvvazvqud43v3p5mmjdpnuzy3gw84h4dwxsfn6dly", 127 | }, 128 | { 129 | types.AddressType32Bytes, 130 | "test2", 131 | "test2", 132 | "cosmos1qmsgyd6yu06uryqtw7t6lg7ua5ll7s3ej828fcqfakrphppug4xqcx7w45", 133 | }, 134 | { 135 | types.AddressType20Bytes, 136 | "", 137 | "test2", 138 | "cosmos1vqcr4c3tnxyxr08rk28n8mkphe6c5gfuk5eh34", 139 | }, 140 | { 141 | types.AddressType20Bytes, 142 | "test2", 143 | "", 144 | "cosmos1vqcr4c3tnxyxr08rk28n8mkphe6c5gfuk5eh34", 145 | }, 146 | { 147 | types.AddressType20Bytes, 148 | "test2", 149 | "test2", 150 | "cosmos15642je7gk5lxugnqx3evj3jgfjdjv3q0nx6wn7", 151 | }, 152 | { 153 | 3, 154 | "test2", 155 | "invalidAddressType", 156 | "", 157 | }, 158 | } 159 | for _, tc := range testCases { 160 | t.Run(tc.name, func(t *testing.T) { 161 | require.Equal(t, tc.expectedAddress, types.DeriveAddress(tc.addressType, tc.moduleName, tc.name).String()) 162 | }) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /x/farming/spec/06_events.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Events 4 | 5 | The `farming` module emits the following events: 6 | 7 | ## EndBlocker 8 | 9 | | Type | Attribute Key | Attribute Value | 10 | |-------------------|----------------------|------------------------| 11 | | plan_terminated | plan_id | {planID} | 12 | | plan_terminated | farming_pool_address | {farmingPoolAddress} | 13 | | plan_terminated | termination_address | {terminationAddress} | 14 | | rewards_allocated | plan_id | {planID} | 15 | | rewards_allocated | amount | {totalAllocatedAmount} | 16 | | rewards_withdrawn | farmer | {farmer} | 17 | | rewards_withdrawn | staking_coin_denom | {stakingCoinDenom} | 18 | | rewards_withdrawn | rewards_coins | {rewardCoins} | 19 | 20 | ## Handlers 21 | 22 | ### MsgCreateFixedAmountPlan 23 | 24 | | Type | Attribute Key | Attribute Value | 25 | |--------------------------|----------------------|--------------------------| 26 | | create_fixed_amount_plan | plan_id | {planID} | 27 | | create_fixed_amount_plan | plan_name | {planName} | 28 | | create_fixed_amount_plan | farming_pool_address | {farmingPoolAddress} | 29 | | create_fixed_amount_plan | start_time | {startTime} | 30 | | create_fixed_amount_plan | end_time | {endTime} | 31 | | create_fixed_amount_plan | epoch_amount | {epochAmount} | 32 | | message | module | farming | 33 | | message | action | create_fixed_amount_plan | 34 | | message | sender | {senderAddress} | 35 | 36 | ### MsgCreateRatioPlan 37 | 38 | | Type | Attribute Key | Attribute Value | 39 | |-------------------|----------------------|----------------------| 40 | | create_ratio_plan | plan_id | {planID} | 41 | | create_ratio_plan | plan_name | {planName} | 42 | | create_ratio_plan | farming_pool_address | {farmingPoolAddress} | 43 | | create_ratio_plan | start_time | {startTime} | 44 | | create_ratio_plan | end_time | {endTime} | 45 | | create_ratio_plan | epoch_ratio | {epochRatio} | 46 | | message | module | farming | 47 | | message | action | create_ratio_plan | 48 | | message | sender | {senderAddress} | 49 | 50 | ### MsgStake 51 | 52 | | Type | Attribute Key | Attribute Value | 53 | |---------|---------------|-----------------| 54 | | stake | farmer | {farmer} | 55 | | stake | staking_coins | {stakingCoins} | 56 | | message | module | farming | 57 | | message | action | stake | 58 | | message | sender | {senderAddress} | 59 | 60 | ### MsgUnstake 61 | 62 | | Type | Attribute Key | Attribute Value | 63 | |-------------------|--------------------|--------------------| 64 | | unstake | farmer | {farmer} | 65 | | unstake | unstaking_coins | {unstakingCoins} | 66 | | rewards_withdrawn | farmer | {farmer} | 67 | | rewards_withdrawn | staking_coin_denom | {stakingCoinDenom} | 68 | | rewards_withdrawn | rewards_coins | {rewardCoins} | 69 | | message | module | farming | 70 | | message | action | unstake | 71 | | message | sender | {senderAddress} | 72 | 73 | ### MsgHarvest 74 | 75 | | Type | Attribute Key | Attribute Value | 76 | |---------|---------------------|---------------------| 77 | | harvest | farmer | {farmer} | 78 | | harvest | staking_coin_denoms | {stakingCoinDenoms} | 79 | | message | module | farming | 80 | | message | action | harvest | 81 | | message | sender | {senderAddress} | 82 | 83 | ### MsgRemovePlan 84 | 85 | | Type | Attribute Key | Attribute Value | 86 | |-------------|---------------|-----------------| 87 | | remove_plan | creator | {creator} | 88 | | remove_plan | plan_id | {planId} | 89 | | message | module | farming | 90 | | message | action | remove_plan | 91 | | message | sender | {senderAddress} | 92 | 93 | ### MsgAdvanceEpoch 94 | 95 | The `MsgAdvanceEpoch` message is for testing purposes only and requires that you build the `farmingd` binary. See [MsgAdvanceEpoch](04_messages.md#MsgAdvanceEpoch). 96 | -------------------------------------------------------------------------------- /x/farming/module_test.go: -------------------------------------------------------------------------------- 1 | package farming_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | 11 | simapp "github.com/tendermint/farming/app" 12 | "github.com/tendermint/farming/x/farming/keeper" 13 | "github.com/tendermint/farming/x/farming/types" 14 | ) 15 | 16 | const ( 17 | denom1 = "denom1" // staking coin denom 1 18 | denom2 = "denom2" // staking coin denom 2 19 | denom3 = "denom3" // epoch amount for a fixed amount plan 20 | ) 21 | 22 | var ( 23 | initialBalances = sdk.NewCoins( 24 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000), 25 | sdk.NewInt64Coin(denom1, 1_000_000_000), 26 | sdk.NewInt64Coin(denom2, 1_000_000_000), 27 | sdk.NewInt64Coin(denom3, 1_000_000_000)) 28 | ) 29 | 30 | type ModuleTestSuite struct { 31 | suite.Suite 32 | 33 | app *simapp.FarmingApp 34 | ctx sdk.Context 35 | keeper keeper.Keeper 36 | querier keeper.Querier 37 | addrs []sdk.AccAddress 38 | sampleFixedAmtPlans []types.PlanI 39 | sampleRatioPlans []types.PlanI 40 | samplePlans []types.PlanI 41 | } 42 | 43 | func TestModuleTestSuite(t *testing.T) { 44 | suite.Run(t, new(ModuleTestSuite)) 45 | } 46 | 47 | func (suite *ModuleTestSuite) SetupTest() { 48 | app := simapp.Setup(false) 49 | ctx := app.BaseApp.NewContext(false, tmproto.Header{}) 50 | 51 | keeper.EnableRatioPlan = true 52 | 53 | suite.app = app 54 | suite.ctx = ctx 55 | suite.keeper = suite.app.FarmingKeeper 56 | suite.querier = keeper.Querier{Keeper: suite.keeper} 57 | suite.addrs = simapp.AddTestAddrs(suite.app, suite.ctx, 6, sdk.ZeroInt()) 58 | for _, addr := range suite.addrs { 59 | err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr, initialBalances) 60 | suite.Require().NoError(err) 61 | } 62 | suite.sampleFixedAmtPlans = []types.PlanI{ 63 | types.NewFixedAmountPlan( 64 | types.NewBasePlan( 65 | 1, 66 | "testPlan1", 67 | types.PlanTypePrivate, 68 | suite.addrs[4].String(), 69 | suite.addrs[4].String(), 70 | sdk.NewDecCoins( 71 | sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)), // 30% 72 | sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1)), // 70% 73 | ), 74 | types.ParseTime("2021-08-02T00:00:00Z"), 75 | types.ParseTime("2021-09-02T00:00:00Z"), 76 | ), 77 | sdk.NewCoins(sdk.NewInt64Coin(denom3, 1_000_000)), 78 | ), 79 | types.NewFixedAmountPlan( 80 | types.NewBasePlan( 81 | 2, 82 | "testPlan2", 83 | types.PlanTypePublic, 84 | suite.addrs[5].String(), 85 | suite.addrs[5].String(), 86 | sdk.NewDecCoins( 87 | sdk.NewDecCoinFromDec(denom1, sdk.OneDec()), // 100% 88 | ), 89 | types.ParseTime("2021-08-04T00:00:00Z"), 90 | types.ParseTime("2021-08-12T00:00:00Z"), 91 | ), 92 | sdk.NewCoins(sdk.NewInt64Coin(denom3, 2_000_000)), 93 | ), 94 | } 95 | suite.sampleRatioPlans = []types.PlanI{ 96 | types.NewRatioPlan( 97 | types.NewBasePlan( 98 | 3, 99 | "testPlan3", 100 | types.PlanTypePrivate, 101 | suite.addrs[4].String(), 102 | suite.addrs[4].String(), 103 | sdk.NewDecCoins( 104 | sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(5, 1)), // 50% 105 | sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(5, 1)), // 50% 106 | ), 107 | types.ParseTime("2021-08-01T00:00:00Z"), 108 | types.ParseTime("2021-08-09T00:00:00Z"), 109 | ), 110 | sdk.NewDecWithPrec(4, 2), // 4% 111 | ), 112 | types.NewRatioPlan( 113 | types.NewBasePlan( 114 | 4, 115 | "testPlan4", 116 | types.PlanTypePublic, 117 | suite.addrs[5].String(), 118 | suite.addrs[5].String(), 119 | sdk.NewDecCoins( 120 | sdk.NewDecCoinFromDec(denom2, sdk.OneDec()), // 100% 121 | ), 122 | types.ParseTime("2021-08-03T00:00:00Z"), 123 | types.ParseTime("2021-08-07T00:00:00Z"), 124 | ), 125 | sdk.NewDecWithPrec(3, 2), // 3% 126 | ), 127 | } 128 | suite.samplePlans = append(suite.sampleFixedAmtPlans, suite.sampleRatioPlans...) 129 | } 130 | 131 | // Stake is a convenient method to test Keeper.Stake. 132 | func (suite *ModuleTestSuite) Stake(farmerAcc sdk.AccAddress, amt sdk.Coins) { 133 | err := suite.keeper.Stake(suite.ctx, farmerAcc, amt) 134 | suite.Require().NoError(err) 135 | } 136 | 137 | // Rewards is a convenient method to test Keeper.WithdrawAllRewards. 138 | func (suite *ModuleTestSuite) Rewards(farmerAcc sdk.AccAddress) sdk.Coins { 139 | cacheCtx, _ := suite.ctx.CacheContext() 140 | rewards, err := suite.keeper.WithdrawAllRewards(cacheCtx, farmerAcc) 141 | suite.Require().NoError(err) 142 | return rewards 143 | } 144 | 145 | func coinsEq(exp, got sdk.Coins) (bool, string, string, string) { 146 | return exp.IsEqual(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() 147 | } 148 | -------------------------------------------------------------------------------- /x/farming/spec/08_proposal.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Proposal 4 | 5 | The `farming` module contains the following public plan governance proposal that receives one of the following requests. 6 | 7 | - `AddPlanRequest` is the request proposal that requests the module to create a public farming plan. You can either input epoch amount `EpochAmount` or epoch ratio `EpochRatio`. Depending on which value of the parameter you input, it creates the following plan type `FixedAmountPlan` or `RatioPlan`. 8 | 9 | - `ModifyPlanRequest` is the request proposal that requests the module to update the plan. You can also update the plan type. 10 | 11 | - `DeletePlanRequest` is the request proposal that requests the module to delete the plan. It sends all remaining coins in the plan's farming pool `FarmingPoolAddress` to the termination address `TerminationAddress` and mark the plan as terminated. 12 | 13 | Note that adding or modifying `RatioPlan`s is disabled by default. 14 | The binary should be built using `make install-testing` command to enable that. 15 | 16 | ## PublicPlanProposal 17 | 18 | ```go 19 | // PublicPlanProposal defines a public farming plan governance proposal that receives one of the following requests: 20 | // A request that creates a public farming plan, a request that updates the plan, and a request that deletes the plan. 21 | // For public plan creation, depending on which field is passed, either epoch amount or epoch ratio, it creates a fixed amount plan or ratio plan. 22 | type PublicPlanProposal struct { 23 | // title specifies the title of the plan 24 | Title string 25 | // description specifies the description of the plan 26 | Description string 27 | // add_plan_requests specifies AddPlanRequest object 28 | AddPlanRequests []AddPlanRequest 29 | // modify_plan_requests specifies ModifyPlanRequest object 30 | ModifyPlanRequests []ModifyPlanRequest 31 | // delete_plan_requests specifies DeletePlanRequest object 32 | DeletePlanRequests []DeletePlanRequest 33 | } 34 | ``` 35 | 36 | ## AddPlanRequest 37 | 38 | Request the module to create a public farming plan. 39 | 40 | - For each request, you must specify epoch amount `EpochAmount` or epoch ratio `EpochRatio`. 41 | - Depending on the value, the plan type `FixedAmountPlan` or `RatioPlan` is created. 42 | 43 | ```go 44 | // AddPlanRequest details a proposal for creating a public plan. 45 | type AddPlanRequest struct { 46 | // name specifies the name of the plan 47 | Name string 48 | // farming_pool_address defines the bech32-encoded address of the farming pool 49 | FarmingPoolAddress string 50 | // termination_address defines the bech32-encoded address that terminates plan 51 | // when the plan ends after the end time, the balance of farming pool address 52 | // is transferred to the termination address 53 | TerminationAddress string 54 | // staking_coin_weights specifies coin weights for the plan 55 | StakingCoinWeights sdk.DecCoins 56 | // start_time specifies the start time of the plan 57 | StartTime time.Time 58 | // end_time specifies the end time of the plan 59 | EndTime time.Time 60 | // epoch_amount specifies the distributing amount for each epoch 61 | EpochAmount sdk.Coins 62 | // epoch_ratio specifies the distributing amount by ratio 63 | EpochRatio sdk.Dec 64 | } 65 | ``` 66 | 67 | ## ModifyPlanRequest 68 | 69 | Request the module to update the plan or the plan type. 70 | 71 | ```go 72 | // ModifyPlanRequest details a proposal for updating an existing public plan. 73 | type ModifyPlanRequest struct { 74 | // plan_id specifies index of the farming plan 75 | PlanId uint64 76 | // name specifies the name of the plan 77 | Name string 78 | // farming_pool_address defines the bech32-encoded address of the farming pool 79 | FarmingPoolAddress string 80 | // termination_address defines the bech32-encoded address that terminates plan 81 | // when the plan ends after the end time, the balance of farming pool address 82 | // is transferred to the termination address 83 | TerminationAddress string 84 | // staking_coin_weights specifies coin weights for the plan 85 | StakingCoinWeights sdk.DecCoins 86 | // start_time specifies the start time of the plan 87 | StartTime time.Time 88 | // end_time specifies the end time of the plan 89 | EndTime time.Time 90 | // epoch_amount specifies the distributing amount for each epoch 91 | EpochAmount sdk.Coins 92 | // epoch_ratio specifies the distributing amount by ratio 93 | EpochRatio sdk.Dec 94 | } 95 | ``` 96 | 97 | ## DeletePlanRequests 98 | 99 | Request the module to delete the plan. All remaining coins in the plan's farming pool `FarmingPoolAddress` are sent to the termination address `TerminationAddress` and the plan is marked as terminated. 100 | 101 | ```go 102 | // DeletePlanRequests details a proposal for deleting an existing public plan. 103 | type DeletePlanRequests struct { 104 | // plan_id specifies index of the farming plan 105 | PlanId uint64 106 | } 107 | ``` -------------------------------------------------------------------------------- /third_party/proto/gogoproto/gogo.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers for Go with Gadgets 2 | // 3 | // Copyright (c) 2013, The GoGo Authors. All rights reserved. 4 | // http://github.com/gogo/protobuf 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // * Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // * Redistributions in binary form must reproduce the above 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | syntax = "proto2"; 30 | package gogoproto; 31 | 32 | import "google/protobuf/descriptor.proto"; 33 | 34 | option java_package = "com.google.protobuf"; 35 | option java_outer_classname = "GoGoProtos"; 36 | option go_package = "github.com/gogo/protobuf/gogoproto"; 37 | 38 | extend google.protobuf.EnumOptions { 39 | optional bool goproto_enum_prefix = 62001; 40 | optional bool goproto_enum_stringer = 62021; 41 | optional bool enum_stringer = 62022; 42 | optional string enum_customname = 62023; 43 | optional bool enumdecl = 62024; 44 | } 45 | 46 | extend google.protobuf.EnumValueOptions { 47 | optional string enumvalue_customname = 66001; 48 | } 49 | 50 | extend google.protobuf.FileOptions { 51 | optional bool goproto_getters_all = 63001; 52 | optional bool goproto_enum_prefix_all = 63002; 53 | optional bool goproto_stringer_all = 63003; 54 | optional bool verbose_equal_all = 63004; 55 | optional bool face_all = 63005; 56 | optional bool gostring_all = 63006; 57 | optional bool populate_all = 63007; 58 | optional bool stringer_all = 63008; 59 | optional bool onlyone_all = 63009; 60 | 61 | optional bool equal_all = 63013; 62 | optional bool description_all = 63014; 63 | optional bool testgen_all = 63015; 64 | optional bool benchgen_all = 63016; 65 | optional bool marshaler_all = 63017; 66 | optional bool unmarshaler_all = 63018; 67 | optional bool stable_marshaler_all = 63019; 68 | 69 | optional bool sizer_all = 63020; 70 | 71 | optional bool goproto_enum_stringer_all = 63021; 72 | optional bool enum_stringer_all = 63022; 73 | 74 | optional bool unsafe_marshaler_all = 63023; 75 | optional bool unsafe_unmarshaler_all = 63024; 76 | 77 | optional bool goproto_extensions_map_all = 63025; 78 | optional bool goproto_unrecognized_all = 63026; 79 | optional bool gogoproto_import = 63027; 80 | optional bool protosizer_all = 63028; 81 | optional bool compare_all = 63029; 82 | optional bool typedecl_all = 63030; 83 | optional bool enumdecl_all = 63031; 84 | 85 | optional bool goproto_registration = 63032; 86 | optional bool messagename_all = 63033; 87 | 88 | optional bool goproto_sizecache_all = 63034; 89 | optional bool goproto_unkeyed_all = 63035; 90 | } 91 | 92 | extend google.protobuf.MessageOptions { 93 | optional bool goproto_getters = 64001; 94 | optional bool goproto_stringer = 64003; 95 | optional bool verbose_equal = 64004; 96 | optional bool face = 64005; 97 | optional bool gostring = 64006; 98 | optional bool populate = 64007; 99 | optional bool stringer = 67008; 100 | optional bool onlyone = 64009; 101 | 102 | optional bool equal = 64013; 103 | optional bool description = 64014; 104 | optional bool testgen = 64015; 105 | optional bool benchgen = 64016; 106 | optional bool marshaler = 64017; 107 | optional bool unmarshaler = 64018; 108 | optional bool stable_marshaler = 64019; 109 | 110 | optional bool sizer = 64020; 111 | 112 | optional bool unsafe_marshaler = 64023; 113 | optional bool unsafe_unmarshaler = 64024; 114 | 115 | optional bool goproto_extensions_map = 64025; 116 | optional bool goproto_unrecognized = 64026; 117 | 118 | optional bool protosizer = 64028; 119 | optional bool compare = 64029; 120 | 121 | optional bool typedecl = 64030; 122 | 123 | optional bool messagename = 64033; 124 | 125 | optional bool goproto_sizecache = 64034; 126 | optional bool goproto_unkeyed = 64035; 127 | } 128 | 129 | extend google.protobuf.FieldOptions { 130 | optional bool nullable = 65001; 131 | optional bool embed = 65002; 132 | optional string customtype = 65003; 133 | optional string customname = 65004; 134 | optional string jsontag = 65005; 135 | optional string moretags = 65006; 136 | optional string casttype = 65007; 137 | optional string castkey = 65008; 138 | optional string castvalue = 65009; 139 | 140 | optional bool stdtime = 65010; 141 | optional bool stdduration = 65011; 142 | optional bool wktpointer = 65012; 143 | 144 | optional string castrepeated = 65013; 145 | } 146 | -------------------------------------------------------------------------------- /x/farming/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gopkg.in/yaml.v2" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/cosmos/cosmos-sdk/types/address" 10 | paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" 11 | ) 12 | 13 | const ( 14 | // PrivatePlanMaxNumDenoms is the maximum number of denoms in a private plan's 15 | // staking coin weights and epoch amount. 16 | PrivatePlanMaxNumDenoms = 50 17 | // PublicPlanMaxNumDenoms is the maximum number of denoms in a public plan's 18 | // staking coin weights and epoch amount. 19 | PublicPlanMaxNumDenoms = 500 20 | ) 21 | 22 | // Parameter store keys 23 | var ( 24 | KeyPrivatePlanCreationFee = []byte("PrivatePlanCreationFee") 25 | KeyNextEpochDays = []byte("NextEpochDays") 26 | KeyFarmingFeeCollector = []byte("FarmingFeeCollector") 27 | KeyDelayedStakingGasFee = []byte("DelayedStakingGasFee") 28 | KeyMaxNumPrivatePlans = []byte("MaxNumPrivatePlans") 29 | 30 | DefaultPrivatePlanCreationFee = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1_000_000_000))) 31 | DefaultCurrentEpochDays = uint32(1) 32 | DefaultNextEpochDays = uint32(1) 33 | DefaultFarmingFeeCollector = sdk.AccAddress(address.Module(ModuleName, []byte("FarmingFeeCollectorAcc"))) 34 | DefaultDelayedStakingGasFee = sdk.Gas(60000) // See https://github.com/tendermint/farming/issues/102 for details. 35 | DefaultMaxNumPrivatePlans = uint32(10000) 36 | 37 | // ReserveAddressType is an address type of reserve accounts for staking or rewards. 38 | // The module uses the address type of 32 bytes length, but it can be changed depending on Cosmos SDK's direction. 39 | // The discussion around this issue can be found in this link. 40 | // https://github.com/tendermint/farming/issues/200 41 | ReserveAddressType = AddressType32Bytes 42 | RewardsReserveAcc = DeriveAddress(ReserveAddressType, ModuleName, RewardReserveAccPrefix) 43 | ) 44 | 45 | var _ paramstypes.ParamSet = (*Params)(nil) 46 | 47 | // ParamKeyTable returns the parameter key table. 48 | func ParamKeyTable() paramstypes.KeyTable { 49 | return paramstypes.NewKeyTable().RegisterParamSet(&Params{}) 50 | } 51 | 52 | // DefaultParams returns the default farming module parameters. 53 | func DefaultParams() Params { 54 | return Params{ 55 | PrivatePlanCreationFee: DefaultPrivatePlanCreationFee, 56 | NextEpochDays: DefaultNextEpochDays, 57 | FarmingFeeCollector: DefaultFarmingFeeCollector.String(), 58 | DelayedStakingGasFee: DefaultDelayedStakingGasFee, 59 | MaxNumPrivatePlans: DefaultMaxNumPrivatePlans, 60 | } 61 | } 62 | 63 | // ParamSetPairs implements paramstypes.ParamSet. 64 | func (p *Params) ParamSetPairs() paramstypes.ParamSetPairs { 65 | return paramstypes.ParamSetPairs{ 66 | paramstypes.NewParamSetPair(KeyPrivatePlanCreationFee, &p.PrivatePlanCreationFee, validatePrivatePlanCreationFee), 67 | paramstypes.NewParamSetPair(KeyNextEpochDays, &p.NextEpochDays, validateNextEpochDays), 68 | paramstypes.NewParamSetPair(KeyFarmingFeeCollector, &p.FarmingFeeCollector, validateFarmingFeeCollector), 69 | paramstypes.NewParamSetPair(KeyDelayedStakingGasFee, &p.DelayedStakingGasFee, validateDelayedStakingGas), 70 | paramstypes.NewParamSetPair(KeyMaxNumPrivatePlans, &p.MaxNumPrivatePlans, validateMaxNumPrivatePlans), 71 | } 72 | } 73 | 74 | // String returns a human-readable string representation of the parameters. 75 | func (p Params) String() string { 76 | out, _ := yaml.Marshal(p) 77 | return string(out) 78 | } 79 | 80 | // Validate validates parameters. 81 | func (p Params) Validate() error { 82 | for _, v := range []struct { 83 | value interface{} 84 | validator func(interface{}) error 85 | }{ 86 | {p.PrivatePlanCreationFee, validatePrivatePlanCreationFee}, 87 | {p.NextEpochDays, validateNextEpochDays}, 88 | {p.FarmingFeeCollector, validateFarmingFeeCollector}, 89 | {p.DelayedStakingGasFee, validateDelayedStakingGas}, 90 | {p.MaxNumPrivatePlans, validateMaxNumPrivatePlans}, 91 | } { 92 | if err := v.validator(v.value); err != nil { 93 | return err 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | func validatePrivatePlanCreationFee(i interface{}) error { 100 | v, ok := i.(sdk.Coins) 101 | if !ok { 102 | return fmt.Errorf("invalid parameter type: %T", i) 103 | } 104 | 105 | if err := v.Validate(); err != nil { 106 | return err 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func validateNextEpochDays(i interface{}) error { 113 | v, ok := i.(uint32) 114 | if !ok { 115 | return fmt.Errorf("invalid parameter type: %T", i) 116 | } 117 | 118 | if v == 0 { 119 | return fmt.Errorf("next epoch days must be positive: %d", v) 120 | } 121 | 122 | return nil 123 | } 124 | 125 | func validateFarmingFeeCollector(i interface{}) error { 126 | v, ok := i.(string) 127 | if !ok { 128 | return fmt.Errorf("invalid parameter type: %T", i) 129 | } 130 | 131 | if v == "" { 132 | return fmt.Errorf("farming fee collector address must not be empty") 133 | } 134 | 135 | _, err := sdk.AccAddressFromBech32(v) 136 | if err != nil { 137 | return fmt.Errorf("invalid account address: %v", v) 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func validateDelayedStakingGas(i interface{}) error { 144 | _, ok := i.(sdk.Gas) 145 | if !ok { 146 | return fmt.Errorf("invalid parameter type: %T", i) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func validateMaxNumPrivatePlans(i interface{}) error { 153 | _, ok := i.(uint32) 154 | if !ok { 155 | return fmt.Errorf("invalid parameter type: %T", i) 156 | } 157 | 158 | // Allow zero MaxNumPrivatePlans 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /x/farming/spec/04_messages.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Messages 4 | 5 | Messages (Msg) are objects that trigger state transitions. Msgs are wrapped in transactions (Txs) that clients submit to the network. The Cosmos SDK wraps and unwraps `farming` module messages from transactions. 6 | 7 | ## MsgCreateFixedAmountPlan 8 | 9 | Anyone can create this private plan type message. 10 | 11 | - A fixed amount plan distributes the amount of coins by a fixed amount that is defined in `EpochAmount`. 12 | - Internally, the private plan's farming pool address is derived and assigned to the plan. 13 | - The plan's `TerminationAddress` is set to the plan creator's address. 14 | - All the coin denoms specified in `StakingCoinWeights` and `EpochAmount` must have positive supply on chain. 15 | 16 | The creator must query the plan and send the amount of coins to the farming pool address so that the plan distributes as intended. 17 | 18 | **Note:** The `PlanCreationFee` must be paid on plan creation to prevent spamming attacks. This fee is refunded when the creator removes the plan by sending `MsgRemovePlan`. 19 | 20 | ```go 21 | type MsgCreateFixedAmountPlan struct { 22 | Name string // name for the plan for display 23 | Creator string // bech32-encoded address of the creator for the private plan 24 | StakingCoinWeights sdk.DecCoins // staking coin weights for the plan 25 | StartTime time.Time // start time of the plan 26 | EndTime time.Time // end time of the plan 27 | EpochAmount sdk.Coins // distributing amount for every epoch 28 | } 29 | ``` 30 | 31 | ## MsgCreateRatioPlan 32 | 33 | ***This message is disabled by default, you have to build the binary with `make install-testing` to activate this message.*** 34 | 35 | Anyone can create this private plan type message. 36 | 37 | - A ratio plan plans to distribute amount of coins by ratio defined in `EpochRatio`. 38 | - Internally, the private plan's farming pool address is derived and assigned to the plan. 39 | - The plan's `TerminationAddress` is set to the plan creator's address. 40 | - All the coin denoms specified in `StakingCoinWeights` must have positive supply on chain. 41 | 42 | The creator must query the plan and send the amount of coins to the farming pool address so that the plan distributes as intended. 43 | 44 | For a ratio plan, whichever coins the farming pool address has in balances are used every epoch. 45 | 46 | **Note:** The `PlanCreationFee` must be paid on plan creation to prevent spamming attacks. This fee is refunded when the creator removes the plan by sending `MsgRemovePlan`. 47 | 48 | 49 | ```go 50 | type MsgCreateRatioPlan struct { 51 | Name string // name for the plan for display 52 | Creator string // bech32-encoded address of the creator for the private plan 53 | StakingCoinWeights sdk.DecCoins // staking coin weights for the plan 54 | StartTime time.Time // start time of the plan 55 | EndTime time.Time // end time of the plan 56 | EpochRatio sdk.Dec // distributing amount by ratio 57 | } 58 | ``` 59 | 60 | ## MsgStake 61 | 62 | A farmer must have sufficient amount of coins to stake. If a farmer stakes coin or coins that are defined in staking the coin weights of plans, then the farmer becomes eligible to receive rewards. 63 | 64 | ```go 65 | type MsgStake struct { 66 | Farmer string // bech32-encoded address of the farmer 67 | StakingCoins sdk.Coins // amount of coins to stake 68 | } 69 | ``` 70 | 71 | ## MsgUnstake 72 | 73 | A farmer must have some staking coins to trigger this message. 74 | 75 | In contrast to the Cosmos SDK [staking](https://github.com/cosmos/cosmos-sdk/blob/master/x/staking/spec/01_state.md) module, there is no concept of an unbonding period where some time is required to unstake coins. 76 | 77 | All of the accumulated farming rewards are automatically withdrawn to the farmer after an unstaking event is triggered. 78 | 79 | ```go 80 | type MsgUnstake struct { 81 | Farmer string // bech32-encoded address of the farmer 82 | UnstakingCoins sdk.Coins // amount of coins to unstake 83 | } 84 | ``` 85 | 86 | ## MsgHarvest 87 | 88 | The farming rewards are automatically accumulated, but they are not automatically distributed. 89 | 90 | A farmer must harvest their farming rewards. This mechanism is similar to the Cosmos SDK [distribution](https://github.com/cosmos/cosmos-sdk/blob/master/x/distribution/spec/01_concepts.md) module. 91 | 92 | ```go 93 | type MsgHarvest struct { 94 | Farmer string // bech32-encoded address of the farmer 95 | StakingCoinDenoms []string // staking coin denoms that the farmer has staked 96 | } 97 | ``` 98 | 99 | ## MsgRemovePlan 100 | 101 | After a private plan is terminated, the plan's creator should remove the plan by sending `MsgRemovePlan`. 102 | By removing a plan, the plan is deleted in the store and the creator gets `PrivatePlanCreationFee` refunded. 103 | 104 | ```go 105 | type MsgRemovePlan struct { 106 | Creator string // bech32-encoded address of the plan creator 107 | PlanId uint64 // id of the plan that is going to be removed 108 | } 109 | ``` 110 | 111 | ## MsgAdvanceEpoch 112 | 113 | ***This message is disabled by default, you have to build the binary with `make install-testing` to activate this message.*** 114 | 115 | For testing purposes only, this custom message is used to advance epoch by 1. 116 | 117 | When you send the `MsgAdvanceEpoch` message to the network, epoch increases by 1. 118 | 119 | ```go 120 | type MsgAdvanceEpoch struct { 121 | Requester string // requester defines the bech32-encoded address of the requester 122 | } 123 | ``` -------------------------------------------------------------------------------- /x/farming/keeper/proposal_handler.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | 7 | "github.com/tendermint/farming/x/farming/types" 8 | ) 9 | 10 | // HandlePublicPlanProposal is a handler for executing a public plan creation proposal. 11 | func HandlePublicPlanProposal(ctx sdk.Context, k Keeper, proposal *types.PublicPlanProposal) error { 12 | if proposal.AddPlanRequests != nil { 13 | if err := k.AddPublicPlanProposal(ctx, proposal.AddPlanRequests); err != nil { 14 | return err 15 | } 16 | } 17 | 18 | if proposal.ModifyPlanRequests != nil { 19 | if err := k.ModifyPublicPlanProposal(ctx, proposal.ModifyPlanRequests); err != nil { 20 | return err 21 | } 22 | } 23 | 24 | if proposal.DeletePlanRequests != nil { 25 | if err := k.DeletePublicPlanProposal(ctx, proposal.DeletePlanRequests); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | plans := k.GetPlans(ctx) 31 | if err := types.ValidateTotalEpochRatio(plans); err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | 38 | // AddPublicPlanProposal adds a new public plan once the governance proposal is passed. 39 | func (k Keeper) AddPublicPlanProposal(ctx sdk.Context, proposals []types.AddPlanRequest) error { 40 | for _, p := range proposals { 41 | farmingPoolAcc, err := sdk.AccAddressFromBech32(p.GetFarmingPoolAddress()) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | terminationAcc, err := sdk.AccAddressFromBech32(p.GetTerminationAddress()) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | if p.IsForFixedAmountPlan() { 52 | msg := types.NewMsgCreateFixedAmountPlan( 53 | p.GetName(), 54 | farmingPoolAcc, 55 | p.GetStakingCoinWeights(), 56 | p.GetStartTime(), 57 | p.GetEndTime(), 58 | p.EpochAmount, 59 | ) 60 | 61 | plan, err := k.CreateFixedAmountPlan(ctx, msg, farmingPoolAcc, terminationAcc, types.PlanTypePublic) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | logger := k.Logger(ctx) 67 | logger.Info("created public fixed amount plan", "fixed_amount_plan", plan) 68 | } else { 69 | if !EnableRatioPlan { 70 | return types.ErrRatioPlanDisabled 71 | } 72 | 73 | msg := types.NewMsgCreateRatioPlan( 74 | p.GetName(), 75 | farmingPoolAcc, 76 | p.GetStakingCoinWeights(), 77 | p.GetStartTime(), 78 | p.GetEndTime(), 79 | p.EpochRatio, 80 | ) 81 | 82 | plan, err := k.CreateRatioPlan(ctx, msg, farmingPoolAcc, terminationAcc, types.PlanTypePublic) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | logger := k.Logger(ctx) 88 | logger.Info("created public ratio amount plan", "ratio_plan", plan) 89 | } 90 | } 91 | 92 | return nil 93 | } 94 | 95 | // ModifyPublicPlanProposal overwrites the plan with the new plan proposal once the governance proposal is passed. 96 | func (k Keeper) ModifyPublicPlanProposal(ctx sdk.Context, proposals []types.ModifyPlanRequest) error { 97 | for _, p := range proposals { 98 | plan, found := k.GetPlan(ctx, p.GetPlanId()) 99 | if !found { 100 | return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "plan %d is not found", p.GetPlanId()) 101 | } 102 | 103 | if plan.GetType() != types.PlanTypePublic { 104 | return sdkerrors.Wrapf(types.ErrInvalidPlanType, "plan %d is not a public plan", p.GetPlanId()) 105 | } 106 | 107 | if p.GetName() != "" { 108 | if err := plan.SetName(p.GetName()); err != nil { 109 | return err 110 | } 111 | } 112 | 113 | if p.GetFarmingPoolAddress() != "" { 114 | farmingPoolAcc, err := sdk.AccAddressFromBech32(p.GetFarmingPoolAddress()) 115 | if err != nil { 116 | return err 117 | } 118 | if err := plan.SetFarmingPoolAddress(farmingPoolAcc); err != nil { 119 | return err 120 | } 121 | } 122 | 123 | if p.GetTerminationAddress() != "" { 124 | terminationAcc, err := sdk.AccAddressFromBech32(p.GetTerminationAddress()) 125 | if err != nil { 126 | return err 127 | } 128 | if err := plan.SetTerminationAddress(terminationAcc); err != nil { 129 | return err 130 | } 131 | } 132 | 133 | if p.GetStakingCoinWeights() != nil { 134 | if err := plan.SetStakingCoinWeights(p.GetStakingCoinWeights()); err != nil { 135 | return err 136 | } 137 | } 138 | 139 | if p.GetStartTime() != nil { 140 | if err := plan.SetStartTime(*p.GetStartTime()); err != nil { 141 | return err 142 | } 143 | } 144 | 145 | if p.GetEndTime() != nil { 146 | if err := plan.SetEndTime(*p.GetEndTime()); err != nil { 147 | return err 148 | } 149 | } 150 | 151 | if p.IsForFixedAmountPlan() { 152 | // change the plan to fixed amount plan 153 | plan = types.NewFixedAmountPlan(plan.GetBasePlan(), p.GetEpochAmount()) 154 | 155 | logger := k.Logger(ctx) 156 | logger.Info("updated public fixed amount plan", "fixed_amount_plan", plan) 157 | 158 | } else if p.IsForRatioPlan() { 159 | if !EnableRatioPlan { 160 | return types.ErrRatioPlanDisabled 161 | } 162 | 163 | // change the plan to ratio plan 164 | plan = types.NewRatioPlan(plan.GetBasePlan(), p.EpochRatio) 165 | 166 | logger := k.Logger(ctx) 167 | logger.Info("updated public ratio plan", "ratio_plan", plan) 168 | } 169 | 170 | k.SetPlan(ctx, plan) 171 | } 172 | 173 | return nil 174 | } 175 | 176 | // DeletePublicPlanProposal deletes public plan proposal once the governance proposal is passed. 177 | func (k Keeper) DeletePublicPlanProposal(ctx sdk.Context, proposals []types.DeletePlanRequest) error { 178 | for _, p := range proposals { 179 | plan, found := k.GetPlan(ctx, p.GetPlanId()) 180 | if !found { 181 | return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "plan %d is not found", p.GetPlanId()) 182 | } 183 | 184 | if plan.GetType() != types.PlanTypePublic { 185 | return sdkerrors.Wrapf(types.ErrInvalidPlanType, "plan %d is not a public plan", p.GetPlanId()) 186 | } 187 | 188 | if err := k.TerminatePlan(ctx, plan); err != nil { 189 | return err 190 | } 191 | 192 | logger := k.Logger(ctx) 193 | logger.Info("removed public ratio plan", "plan_id", plan.GetId()) 194 | } 195 | 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /x/farming/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | // NewGenesisState returns new GenesisState. 11 | func NewGenesisState( 12 | params Params, globalPlanId uint64, plans []PlanRecord, 13 | stakings []StakingRecord, queuedStakings []QueuedStakingRecord, totalStakings []TotalStakingsRecord, 14 | historicalRewards []HistoricalRewardsRecord, outstandingRewards []OutstandingRewardsRecord, 15 | currentEpochs []CurrentEpochRecord, rewardPoolCoins sdk.Coins, 16 | lastEpochTime *time.Time, currentEpochDays uint32, 17 | ) *GenesisState { 18 | return &GenesisState{ 19 | Params: params, 20 | GlobalPlanId: globalPlanId, 21 | PlanRecords: plans, 22 | StakingRecords: stakings, 23 | QueuedStakingRecords: queuedStakings, 24 | TotalStakingsRecords: totalStakings, 25 | HistoricalRewardsRecords: historicalRewards, 26 | OutstandingRewardsRecords: outstandingRewards, 27 | CurrentEpochRecords: currentEpochs, 28 | RewardPoolCoins: rewardPoolCoins, 29 | LastEpochTime: lastEpochTime, 30 | CurrentEpochDays: currentEpochDays, 31 | } 32 | } 33 | 34 | // DefaultGenesisState returns the default genesis state. 35 | func DefaultGenesisState() *GenesisState { 36 | return NewGenesisState( 37 | DefaultParams(), 38 | 0, 39 | []PlanRecord{}, 40 | []StakingRecord{}, 41 | []QueuedStakingRecord{}, 42 | []TotalStakingsRecord{}, 43 | []HistoricalRewardsRecord{}, 44 | []OutstandingRewardsRecord{}, 45 | []CurrentEpochRecord{}, 46 | sdk.Coins{}, 47 | nil, 48 | DefaultCurrentEpochDays, 49 | ) 50 | } 51 | 52 | // ValidateGenesis validates GenesisState. 53 | func ValidateGenesis(data GenesisState) error { 54 | if err := data.Params.Validate(); err != nil { 55 | return err 56 | } 57 | 58 | var plans []PlanI 59 | for _, record := range data.PlanRecords { 60 | if err := record.Validate(); err != nil { 61 | return err 62 | } 63 | plan, _ := UnpackPlan(&record.Plan) 64 | if plan.GetId() > data.GlobalPlanId { 65 | return fmt.Errorf("plan id is greater than the global last plan id") 66 | } 67 | plans = append(plans, plan) 68 | } 69 | 70 | if err := ValidateTotalEpochRatio(plans); err != nil { 71 | return err 72 | } 73 | 74 | for _, record := range data.StakingRecords { 75 | if err := record.Validate(); err != nil { 76 | return err 77 | } 78 | } 79 | 80 | for _, record := range data.QueuedStakingRecords { 81 | if err := record.Validate(); err != nil { 82 | return err 83 | } 84 | } 85 | 86 | for _, record := range data.TotalStakingsRecords { 87 | if err := record.Validate(); err != nil { 88 | return err 89 | } 90 | } 91 | 92 | for _, record := range data.HistoricalRewardsRecords { 93 | if err := record.Validate(); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | for _, record := range data.OutstandingRewardsRecords { 99 | if err := record.Validate(); err != nil { 100 | return err 101 | } 102 | } 103 | 104 | for _, record := range data.CurrentEpochRecords { 105 | if err := record.Validate(); err != nil { 106 | return err 107 | } 108 | } 109 | 110 | if err := data.RewardPoolCoins.Validate(); err != nil { 111 | return err 112 | } 113 | 114 | if data.CurrentEpochDays == 0 { 115 | return fmt.Errorf("current epoch days must be positive") 116 | } 117 | 118 | return nil 119 | } 120 | 121 | // Validate validates PlanRecord. 122 | func (record PlanRecord) Validate() error { 123 | plan, err := UnpackPlan(&record.Plan) 124 | if err != nil { 125 | return err 126 | } 127 | if err := plan.Validate(); err != nil { 128 | return err 129 | } 130 | if err := record.FarmingPoolCoins.Validate(); err != nil { 131 | return err 132 | } 133 | return nil 134 | } 135 | 136 | // Validate validates StakingRecord. 137 | func (record StakingRecord) Validate() error { 138 | if _, err := sdk.AccAddressFromBech32(record.Farmer); err != nil { 139 | return err 140 | } 141 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 142 | return err 143 | } 144 | if !record.Staking.Amount.IsPositive() { 145 | return fmt.Errorf("staking amount must be positive: %s", record.Staking.Amount) 146 | } 147 | return nil 148 | } 149 | 150 | // Validate validates StakingRecord. 151 | func (record TotalStakingsRecord) Validate() error { 152 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 153 | return err 154 | } 155 | if !record.Amount.IsPositive() { 156 | return fmt.Errorf("total staking amount must be positive: %s", record.Amount) 157 | } 158 | if err := record.StakingReserveCoins.Validate(); err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | 164 | // Validate validates QueuedStakingRecord. 165 | func (record QueuedStakingRecord) Validate() error { 166 | if _, err := sdk.AccAddressFromBech32(record.Farmer); err != nil { 167 | return err 168 | } 169 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 170 | return err 171 | } 172 | if !record.QueuedStaking.Amount.IsPositive() { 173 | return fmt.Errorf("queued staking amount must be positive: %s", record.QueuedStaking.Amount) 174 | } 175 | return nil 176 | } 177 | 178 | // Validate validates HistoricalRewardsRecord. 179 | func (record HistoricalRewardsRecord) Validate() error { 180 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 181 | return err 182 | } 183 | if err := record.HistoricalRewards.CumulativeUnitRewards.Validate(); err != nil { 184 | return err 185 | } 186 | return nil 187 | } 188 | 189 | // Validate validates OutstandingRewardsRecord. 190 | func (record OutstandingRewardsRecord) Validate() error { 191 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 192 | return err 193 | } 194 | if err := record.OutstandingRewards.Rewards.Validate(); err != nil { 195 | return err 196 | } 197 | return nil 198 | } 199 | 200 | // Validate validates CurrentEpochRecord. 201 | func (record CurrentEpochRecord) Validate() error { 202 | if err := sdk.ValidateDenom(record.StakingCoinDenom); err != nil { 203 | return err 204 | } 205 | return nil 206 | } 207 | -------------------------------------------------------------------------------- /proto/tendermint/farming/v1beta1/proposal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cosmos.farming.v1beta1; 4 | 5 | import "gogoproto/gogo.proto"; 6 | import "cosmos_proto/cosmos.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | import "tendermint/farming/v1beta1/farming.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | 11 | option go_package = "github.com/tendermint/farming/x/farming/types"; 12 | 13 | // PublicPlanProposal defines a public farming plan governance proposal that receives one of the following requests: 14 | // A request that creates a public farming plan, a request that updates the plan, and a request that deletes the plan. 15 | // For public plan creation, depending on which field is passed, either epoch amount or epoch ratio, it creates a fixed 16 | // amount plan or ratio plan. 17 | message PublicPlanProposal { 18 | option (gogoproto.goproto_getters) = false; 19 | option (gogoproto.goproto_stringer) = false; 20 | 21 | // title specifies the title of the plan 22 | string title = 1; 23 | 24 | // description specifies the description of the plan 25 | string description = 2; 26 | 27 | // add_plan_requests specifies AddPlanRequest object 28 | repeated AddPlanRequest add_plan_requests = 3 29 | [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"add_plan_requests\""]; 30 | 31 | // modify_plan_requests specifies ModifyPlanRequest object 32 | repeated ModifyPlanRequest modify_plan_requests = 4 33 | [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"modify_plan_requests\""]; 34 | 35 | // delete_plan_requests specifies DeletePlanRequest object 36 | repeated DeletePlanRequest delete_plan_requests = 5 37 | [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"delete_plan_requests\""]; 38 | } 39 | 40 | // AddPlanRequest details a proposal for creating a public plan. 41 | message AddPlanRequest { 42 | // name specifies the plan name for display 43 | string name = 1; 44 | 45 | // farming_pool_address defines the bech32-encoded address of the farming pool 46 | string farming_pool_address = 2 [(gogoproto.moretags) = "yaml:\"farming_pool_address\""]; 47 | 48 | // termination_address defines the bech32-encoded address that terminates plan 49 | // when the plan ends after the end time, the balance of farming pool address 50 | // is transferred to the termination address 51 | string termination_address = 3 [(gogoproto.moretags) = "yaml:\"termination_address\""]; 52 | 53 | // staking_coin_weights specifies coin weights for the plan 54 | repeated cosmos.base.v1beta1.DecCoin staking_coin_weights = 4 [ 55 | (gogoproto.moretags) = "yaml:\"staking_coin_weights\"", 56 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", 57 | (gogoproto.nullable) = false 58 | ]; 59 | 60 | // start_time specifies the start time of the plan 61 | google.protobuf.Timestamp start_time = 5 62 | [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"start_time\""]; 63 | 64 | // end_time specifies the end time of the plan 65 | google.protobuf.Timestamp end_time = 6 66 | [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"end_time\""]; 67 | 68 | // epoch_amount specifies the distributing amount for each epoch 69 | repeated cosmos.base.v1beta1.Coin epoch_amount = 7 [ 70 | (gogoproto.moretags) = "yaml:\"epoch_amount\"", 71 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", 72 | (gogoproto.nullable) = false 73 | ]; 74 | 75 | // epoch_ratio specifies the distributing amount by ratio 76 | string epoch_ratio = 8 [ 77 | (gogoproto.moretags) = "yaml:\"epoch_ratio\"", 78 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 79 | (gogoproto.nullable) = false 80 | ]; 81 | } 82 | 83 | // ModifyPlanRequest details a proposal for modifying the existing public plan. 84 | message ModifyPlanRequest { 85 | // plan_id specifies index of the farming plan 86 | uint64 plan_id = 1; 87 | 88 | // name specifies the plan name for display 89 | string name = 2; 90 | 91 | // farming_pool_address defines the bech32-encoded address of the farming pool 92 | string farming_pool_address = 3 [(gogoproto.moretags) = "yaml:\"farming_pool_address\""]; 93 | 94 | // termination_address defines the bech32-encoded address that terminates plan 95 | // when the plan ends after the end time, the balance of farming pool address 96 | // is transferred to the termination address 97 | string termination_address = 4 [(gogoproto.moretags) = "yaml:\"termination_address\""]; 98 | 99 | // staking_coin_weights specifies coin weights for the plan 100 | repeated cosmos.base.v1beta1.DecCoin staking_coin_weights = 5 [ 101 | (gogoproto.moretags) = "yaml:\"staking_coin_weights\"", 102 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", 103 | (gogoproto.nullable) = false 104 | ]; 105 | 106 | // start_time specifies the start time of the plan 107 | google.protobuf.Timestamp start_time = 6 108 | [(gogoproto.stdtime) = true, (gogoproto.nullable) = true, (gogoproto.moretags) = "yaml:\"start_time\""]; 109 | 110 | // end_time specifies the end time of the plan 111 | google.protobuf.Timestamp end_time = 7 112 | [(gogoproto.stdtime) = true, (gogoproto.nullable) = true, (gogoproto.moretags) = "yaml:\"end_time\""]; 113 | 114 | // epoch_amount specifies the distributing amount for each epoch 115 | repeated cosmos.base.v1beta1.Coin epoch_amount = 8 [ 116 | (gogoproto.moretags) = "yaml:\"epoch_amount\"", 117 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", 118 | (gogoproto.nullable) = false 119 | ]; 120 | 121 | // epoch_ratio specifies the distributing amount by ratio 122 | string epoch_ratio = 9 [ 123 | (gogoproto.moretags) = "yaml:\"epoch_ratio\"", 124 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 125 | (gogoproto.nullable) = false 126 | ]; 127 | } 128 | 129 | // DeletePlanRequest details a proposal for deleting an existing public plan. 130 | message DeletePlanRequest { 131 | // plan_id specifies index of the farming plan 132 | uint64 plan_id = 1; 133 | } 134 | --------------------------------------------------------------------------------