├── version.go ├── AUTHORS ├── app ├── test │ └── genesis_ok_ch_admin_test.json ├── app.go └── app_test.go ├── COPYING ├── .gitignore ├── .travis.yml ├── commands ├── version.go ├── exportPub.go ├── createOperator.go ├── createAsset.go └── createAdmin.go ├── circle.yml ├── README.md ├── types ├── wire.go ├── genesis.go ├── errors.go ├── ccy_test.go ├── entity.go ├── account_test.go ├── genesis_test.go ├── account.go ├── ccy.go ├── msgs.go ├── handler.go ├── msgs_test.go └── handler_test.go ├── Gopkg.toml ├── Makefile ├── cmd ├── clearchainctl │ └── main.go └── clearchaind │ └── main.go ├── LICENSE └── Gopkg.lock /version.go: -------------------------------------------------------------------------------- 1 | package clearchain 2 | 3 | var Version string 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Alessio Treglia 2 | Cesar Espinosa 3 | -------------------------------------------------------------------------------- /app/test/genesis_ok_ch_admin_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ch_admin": 3 | { 4 | "public_key":"01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f874", 5 | "entity_name":"ClearChain" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This software is 2 | 3 | Copyright (C) 2016 Alessio Treglia 4 | Baxter-IT (https://www.baxter-it.com/) 5 | 6 | and distributed under the terms of the Apache License, Version 2.0. 7 | 8 | You should have received a copy of the license along with this package; 9 | if not, you may find it at 'https://www.apache.org/licenses/LICENSE-2.0` 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | *.exe 21 | *.test 22 | *.prof 23 | 24 | .vscode/ 25 | 26 | .project 27 | 28 | /vendor 29 | /.idea 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10" 5 | 6 | matrix: 7 | - fast_finish: true 8 | 9 | install: 10 | - export GOPATH=$HOME/gopath 11 | - export PATH=$HOME/gopath/bin:$PATH 12 | - mkdir -p $GOPATH/bin 13 | - wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 -O $GOPATH/bin/dep && chmod +x $GOPATH/bin/dep 14 | 15 | script: 16 | - export GOPATH=$HOME/gopath 17 | - export PATH=$HOME/gopath/bin:$PATH 18 | - make 19 | 20 | after_success: 21 | - bash tools/codecov 22 | -------------------------------------------------------------------------------- /commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/tendermint/clearchain" 9 | ) 10 | 11 | var ( 12 | // VersionCmd prints the program's version to stderr and exits. 13 | VersionCmd = &cobra.Command{ 14 | Use: "version", 15 | Short: "Print clearchain's version", 16 | Run: doVersionCmd, 17 | } 18 | ) 19 | 20 | func doVersionCmd(cmd *cobra.Command, args []string) { 21 | v := clearchain.Version 22 | if len(v) == 0 { 23 | fmt.Fprintln(os.Stderr, "unset") 24 | return 25 | } 26 | fmt.Fprintln(os.Stderr, v) 27 | } 28 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | environment: 3 | GOPATH: "$HOME/.go_workspace" 4 | PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME" 5 | REPO: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME" 6 | PATH: "$GOPATH/bin:$PATH" 7 | hosts: 8 | circlehost: 127.0.0.1 9 | localhost: 127.0.0.1 10 | 11 | dependencies: 12 | pre: 13 | - mkdir -p $GOPATH/bin 14 | - wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 -O $GOPATH/bin/dep && chmod +x $GOPATH/bin/dep 15 | override: 16 | - go version 17 | - mkdir -p "$PROJECT_PARENT_PATH" 18 | - ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$REPO" 19 | - env 20 | 21 | test: 22 | override: 23 | - "cd $REPO && make" 24 | - ls $GOPATH/bin 25 | - bash <(curl -s https://codecov.io/bash) -f coverage.txt 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clearchain 2 | 3 | [![Travis-CI 4 | Status](https://api.travis-ci.org/tendermint/clearchain.png?branch=master)](http://travis-ci.org/tendermint/clearchain) 5 | [![CircleCI](https://circleci.com/gh/tendermint/clearchain/tree/master.svg?style=shield)](https://circleci.com/gh/tendermint/clearchain/tree/master) 6 | [![GoDoc](https://godoc.org/github.com/tendermint/clearchain?status.svg)](https://godoc.org/github.com/tendermint/clearchain) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/tendermint/clearchain)](https://goreportcard.com/report/github.com/tendermint/clearchain) 8 | [![codecov](https://codecov.io/gh/tendermint/clearchain/branch/master/graph/badge.svg)](https://codecov.io/gh/tendermint/clearchain) 9 | [![license](https://img.shields.io/github/license/tendermint/tendermint.svg)](https://github.com/tendermint/clearchain/blob/master/LICENSE) 10 | 11 | Distributed ledger implemented as a Cosmos application for 12 | money transfers that supports multi currency accounts. 13 | 14 | # Get started 15 | 16 | ``` 17 | $ make 18 | ```` 19 | -------------------------------------------------------------------------------- /types/wire.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/wire" 6 | oldwire "github.com/tendermint/go-wire" 7 | ) 8 | 9 | const ( 10 | typeDepositMsg = 0x1 11 | typeSettleMsg = 0x2 12 | typeWithdrawMsg = 0x3 13 | typeCreateAdminMsg = 0x4 14 | typeCreateOperatorMsg = 0x5 15 | typeCreateAssetAccountMsg = 0x6 16 | typeFreezeAdminMsg = 0x7 17 | typeFreezeOperatorMsg = 0x8 18 | 19 | typeAppAccount = 0x1 20 | ) 21 | 22 | // MakeCodec instantiate a wire.Codec and register 23 | // all application's types; it returns the new codec. 24 | func MakeCodec() *wire.Codec { 25 | var _ = oldwire.RegisterInterface( 26 | struct{ sdk.Msg }{}, 27 | oldwire.ConcreteType{DepositMsg{}, typeDepositMsg}, 28 | oldwire.ConcreteType{SettleMsg{}, typeSettleMsg}, 29 | oldwire.ConcreteType{WithdrawMsg{}, typeWithdrawMsg}, 30 | oldwire.ConcreteType{CreateAdminMsg{}, typeCreateAdminMsg}, 31 | oldwire.ConcreteType{CreateOperatorMsg{}, typeCreateOperatorMsg}, 32 | oldwire.ConcreteType{CreateAssetAccountMsg{}, typeCreateAssetAccountMsg}, 33 | oldwire.ConcreteType{FreezeAdminMsg{}, typeFreezeAdminMsg}, 34 | oldwire.ConcreteType{FreezeOperatorMsg{}, typeFreezeOperatorMsg}, 35 | ) 36 | var _ = oldwire.RegisterInterface( 37 | struct{ sdk.Account }{}, 38 | oldwire.ConcreteType{&AppAccount{}, typeAppAccount}, 39 | ) 40 | return wire.NewCodec() 41 | } 42 | -------------------------------------------------------------------------------- /types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | crypto "github.com/tendermint/go-crypto" 7 | ) 8 | 9 | // GenesisState defines the app's initial state to unmarshal. 10 | type GenesisState struct { 11 | ClearingHouseAdmin GenesisAccount `json:"ch_admin"` 12 | } 13 | 14 | // GenesisAccount is an abstraction of the accounts specified in a genesis file 15 | type GenesisAccount struct { 16 | PubKeyHexa string `json:"public_key"` 17 | EntityName string `json:"entity_name"` 18 | } 19 | 20 | // ToClearingHouseAdmin converts a GenesisAccount into an AppAccount (a Clearing House admin user) 21 | func (ga *GenesisAccount) ToClearingHouseAdmin() (acc *AppAccount, err error) { 22 | // Done manually since JSON Unmarshalling does not create a PubKey from a hexa value 23 | pubBytes, err := hex.DecodeString(ga.PubKeyHexa) 24 | if err != nil { 25 | return nil, err 26 | } 27 | publicKey, err := crypto.PubKeyFromBytes(pubBytes) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | adminUser := NewAdminUser(publicKey, nil, ga.EntityName, EntityClearingHouse) 33 | return adminUser, nil 34 | } 35 | 36 | // PubKeyFromHexString converts a hexadecimal string representation of 37 | // a public key into a crypto.PubKey instance. 38 | func PubKeyFromHexString(s string) (crypto.PubKey, error) { 39 | bytes, err := hex.DecodeString(s) 40 | if err != nil { 41 | return crypto.PubKey{}, err 42 | } 43 | return crypto.PubKeyFromBytes(bytes) 44 | } 45 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/cosmos/cosmos-sdk" 30 | version = "~0.13.1" 31 | 32 | [[constraint]] 33 | name = "github.com/spf13/cobra" 34 | version = "~0.0.2" 35 | 36 | [[constraint]] 37 | name = "github.com/stretchr/testify" 38 | version = "~1.2.1" 39 | 40 | [[constraint]] 41 | name = "github.com/tendermint/abci" 42 | version = "~0.10.2" 43 | 44 | [[constraint]] 45 | name = "github.com/tendermint/go-crypto" 46 | version = "~0.5.0" 47 | 48 | [[constraint]] 49 | name = "github.com/tendermint/go-wire" 50 | source = "github.com/tendermint/go-amino" 51 | version = "~0.7.3" 52 | 53 | [[constraint]] 54 | name = "github.com/tendermint/tmlibs" 55 | version = "~0.7.1" 56 | 57 | [prune] 58 | go-tests = true 59 | unused-packages = true 60 | -------------------------------------------------------------------------------- /commands/exportPub.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/spf13/viper" 8 | 9 | "github.com/cosmos/cosmos-sdk/client/keys" 10 | wire "github.com/cosmos/cosmos-sdk/wire" 11 | "github.com/pkg/errors" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const ( 16 | flagFormat = "format" 17 | ) 18 | 19 | func GetExportPubCmd(cdc *wire.Codec) *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "export-pub", 22 | RunE: exportPubCmd, 23 | } 24 | cmd.Flags().String(flagFormat, "", "Alternative output format (armor|json)") 25 | return cmd 26 | } 27 | 28 | func exportPubCmd(cmd *cobra.Command, args []string) error { 29 | if len(args) < 1 { 30 | return fmt.Errorf("insufficient arguments") 31 | } 32 | if len(args) > 1 { 33 | return fmt.Errorf("too many arguments: %v", args) 34 | } 35 | name := args[0] 36 | keybase, err := keys.GetKeyBase() 37 | if err != nil { 38 | return err 39 | } 40 | if viper.GetString(flagFormat) == "armor" { 41 | out, err := keybase.Export(name) 42 | if err != nil { 43 | return err 44 | } 45 | fmt.Println(out) 46 | return nil 47 | } 48 | info, err := keybase.Get(name) 49 | if err != nil { 50 | return errors.Errorf("No key for: %s", name) 51 | } 52 | if viper.GetString(flagFormat) == "json" { 53 | out, err := info.PubKey.MarshalJSON() 54 | if err != nil { 55 | return err 56 | } 57 | fmt.Println(string(out)) 58 | return nil 59 | } 60 | fmt.Println(hex.EncodeToString(info.PubKey.Bytes())) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGES=$(shell go list ./... | grep -v '/vendor/') 2 | BUILD_FLAGS = -ldflags "-X github.com/tendermint/clearchain.Version=`git describe`" 3 | TARGETS = clearchainctl clearchaind 4 | 5 | all: dist-clean get_vendor_deps build test 6 | 7 | ######################################## 8 | ### Build 9 | 10 | build: $(TARGETS) 11 | 12 | clearchaind: 13 | go build $(BUILD_FLAGS) ./cmd/clearchaind 14 | clearchainctl: 15 | go build $(BUILD_FLAGS) ./cmd/clearchainctl 16 | 17 | 18 | ######################################## 19 | ### Tools & dependencies 20 | 21 | get_vendor_deps: 22 | dep ensure -v 23 | 24 | dep: $(GOPATH)/bin/dep 25 | $(GOPATH)/bin/dep: 26 | mkdir -p $(GOPATH)/bin 27 | wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 -O $@ && chmod +x $@ 28 | 29 | 30 | ######################################## 31 | ### Testing 32 | 33 | test: coverage.txt 34 | coverage.txt: clean 35 | touch $@ 36 | for p in $(PACKAGES); do \ 37 | rm -f profile.out ;\ 38 | go test -v -race -coverprofile=profile.out -covermode=atomic $$p;\ 39 | [ ! -f profile.out ] || \ 40 | ( cat profile.out >> $@ ; rm profile.out ) \ 41 | done 42 | 43 | 44 | dist-clean: clean clean-vendor 45 | clean: clean-arch clean-noarch 46 | 47 | clean-arch: 48 | rm -f $(TARGETS) 49 | 50 | clean-noarch: 51 | rm -f profile.out coverage.txt 52 | 53 | clean-vendor: 54 | @echo "--> Purge vendor/ directory" 55 | rm -rf vendor/ 56 | 57 | benchmark: 58 | @go test -bench=. $(PACKAGES) 59 | 60 | 61 | # To avoid unintended conflicts with file names, always add to .PHONY 62 | # unless there is a reason not to. 63 | # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html 64 | .PHONY: build dep get_vendor_deps test benchmark clean clean-arch clean-noarch clean-vendor dist-clean 65 | -------------------------------------------------------------------------------- /commands/createOperator.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/cosmos/cosmos-sdk/client/builder" 8 | "github.com/cosmos/cosmos-sdk/client/keys" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | wire "github.com/cosmos/cosmos-sdk/wire" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/viper" 13 | "github.com/tendermint/clearchain/types" 14 | ) 15 | 16 | // GetCreateOperatorTxCmd returns a createOperatorTxCmd. 17 | func GetCreateOperatorTxCmd(cdc *wire.Codec) *cobra.Command { 18 | cmdr := Commander{Cdc: cdc} 19 | cmd := &cobra.Command{ 20 | Use: "create-operator", 21 | Short: "Create and sign a CreateOperatorTx", 22 | RunE: cmdr.createOperatorTxCmd, 23 | Args: cobra.ExactArgs(1), 24 | } 25 | cmd.Flags().String(flagPubKey, "", "New operator's pubkey") 26 | cmd.Flags().Int64(flagSequence, 0, "Sequence number") 27 | return cmd 28 | } 29 | 30 | func (c Commander) createOperatorTxCmd(cmd *cobra.Command, args []string) error { 31 | name := args[0] 32 | keybase, err := keys.GetKeyBase() 33 | if err != nil { 34 | return nil 35 | } 36 | info, err := keybase.Get(name) 37 | if err != nil { 38 | return err 39 | } 40 | creator := info.PubKey.Address() 41 | msg, err := buildCreateOperatorMsg(creator) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | res, err := builder.SignBuildBroadcast(name, msg, c.Cdc) 47 | if err != nil { 48 | return err 49 | } 50 | fmt.Fprintf(os.Stderr, "Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) 51 | return nil 52 | 53 | } 54 | 55 | func buildCreateOperatorMsg(creator sdk.Address) (sdk.Msg, error) { 56 | // parse new account pubkey 57 | pubKey, err := types.PubKeyFromHexString(viper.GetString(flagPubKey)) 58 | if err != nil { 59 | return nil, err 60 | } 61 | msg := types.NewCreateOperatorMsg(creator, pubKey) 62 | return msg, nil 63 | } 64 | -------------------------------------------------------------------------------- /commands/createAsset.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/cosmos/cosmos-sdk/client/builder" 8 | "github.com/cosmos/cosmos-sdk/client/keys" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | wire "github.com/cosmos/cosmos-sdk/wire" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/viper" 13 | "github.com/tendermint/clearchain/types" 14 | ) 15 | 16 | // GetCreateAssetAccountTxCmd returns a createAssetAccountTxCmd. 17 | func GetCreateAssetAccountTxCmd(cdc *wire.Codec) *cobra.Command { 18 | cmdr := Commander{Cdc: cdc} 19 | cmd := &cobra.Command{ 20 | Use: "create-asset", 21 | Short: "Create and sign a CreateAssetAccountTx", 22 | RunE: cmdr.createAssetAccountTxCmd, 23 | Args: cobra.ExactArgs(1), 24 | } 25 | cmd.Flags().String(flagPubKey, "", "New assset account's pubkey") 26 | cmd.Flags().Int64(flagSequence, 0, "Sequence number") 27 | return cmd 28 | } 29 | 30 | func (c Commander) createAssetAccountTxCmd(cmd *cobra.Command, args []string) error { 31 | name := args[0] 32 | keybase, err := keys.GetKeyBase() 33 | if err != nil { 34 | return nil 35 | } 36 | info, err := keybase.Get(name) 37 | if err != nil { 38 | return err 39 | } 40 | creator := info.PubKey.Address() 41 | msg, err := buildCreateAssetAccountMsg(creator) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | res, err := builder.SignBuildBroadcast(name, msg, c.Cdc) 47 | if err != nil { 48 | return err 49 | } 50 | fmt.Fprintf(os.Stderr, "Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) 51 | return nil 52 | 53 | } 54 | 55 | func buildCreateAssetAccountMsg(creator sdk.Address) (sdk.Msg, error) { 56 | // parse new account pubkey 57 | pubKey, err := types.PubKeyFromHexString(viper.GetString(flagPubKey)) 58 | if err != nil { 59 | return nil, err 60 | } 61 | msg := types.NewCreateAssetAccountMsg(creator, pubKey) 62 | return msg, nil 63 | } 64 | -------------------------------------------------------------------------------- /cmd/clearchainctl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/client/keys" 8 | "github.com/cosmos/cosmos-sdk/client/lcd" 9 | "github.com/cosmos/cosmos-sdk/client/rpc" 10 | "github.com/cosmos/cosmos-sdk/client/tx" 11 | authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" 12 | "github.com/spf13/cobra" 13 | "github.com/tendermint/clearchain/commands" 14 | "github.com/tendermint/clearchain/types" 15 | "github.com/tendermint/tmlibs/cli" 16 | ) 17 | 18 | const ( 19 | defaultConfigBaseDir = ".clearchainctl" 20 | ) 21 | 22 | var ( 23 | clearchainctlCmd = &cobra.Command{ 24 | Use: "clearchainctl", 25 | Short: "Clearchain light-client", 26 | } 27 | ) 28 | 29 | func main() { 30 | cobra.EnableCommandSorting = false 31 | cdc := types.MakeCodec() 32 | rpc.AddCommands(clearchainctlCmd) 33 | clearchainctlCmd.AddCommand(client.LineBreak) 34 | tx.AddCommands(clearchainctlCmd, cdc) 35 | clearchainctlCmd.AddCommand(client.LineBreak) 36 | 37 | // add clearchain-specific commands 38 | clearchainctlCmd.AddCommand( 39 | client.GetCommands( 40 | authcmd.GetAccountCmd("main", cdc, types.GetAccountDecoder(cdc)), 41 | )...) 42 | clearchainctlCmd.AddCommand( 43 | client.PostCommands( 44 | commands.GetCreateAdminTxCmd(cdc), 45 | commands.GetCreateOperatorTxCmd(cdc), 46 | commands.GetCreateAssetAccountTxCmd(cdc), 47 | )...) 48 | clearchainctlCmd.AddCommand(commands.GetExportPubCmd(cdc)) 49 | //clearchainctlCmd.AddCommand(commands.GetImportPubCmd(cdc)) 50 | 51 | // add proxy, version and key info 52 | clearchainctlCmd.AddCommand( 53 | client.LineBreak, 54 | lcd.ServeCommand(cdc), 55 | keys.Commands(), 56 | client.LineBreak, 57 | commands.VersionCmd, 58 | ) 59 | 60 | // prepare and add flags 61 | executor := cli.PrepareMainCmd(clearchainctlCmd, "CC", os.ExpandEnv(defaultConfigBaseDir)) 62 | executor.Execute() 63 | } 64 | -------------------------------------------------------------------------------- /commands/createAdmin.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/cosmos/cosmos-sdk/client/builder" 8 | "github.com/cosmos/cosmos-sdk/client/keys" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | wire "github.com/cosmos/cosmos-sdk/wire" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/viper" 13 | "github.com/tendermint/clearchain/types" 14 | ) 15 | 16 | const ( 17 | flagPubKey = "pubkey" 18 | flagEntityName = "entityname" 19 | flagEntityType = "entitytype" 20 | flagSequence = "seq" 21 | ) 22 | 23 | type Commander struct { 24 | Cdc *wire.Codec 25 | } 26 | 27 | // GetCreateAdminTxCmd returns a CreateAdminTxCmd. 28 | func GetCreateAdminTxCmd(cdc *wire.Codec) *cobra.Command { 29 | cmdr := Commander{Cdc: cdc} 30 | cmd := &cobra.Command{ 31 | Use: "create-admin", 32 | Short: "Create and sign a CreateAdminTx", 33 | RunE: cmdr.createAdminTxCmd, 34 | Args: cobra.ExactArgs(1), 35 | } 36 | cmd.Flags().String(flagPubKey, "", "New admin's pubkey") 37 | cmd.Flags().String(flagEntityName, "", "New admin's entity name") 38 | cmd.Flags().String(flagEntityType, "", "New admin's entity type (ch|gcm|icm|custodian)") 39 | cmd.Flags().Int64(flagSequence, 0, "Sequence number") 40 | return cmd 41 | } 42 | 43 | func (c Commander) createAdminTxCmd(cmd *cobra.Command, args []string) error { 44 | name := args[0] 45 | keybase, err := keys.GetKeyBase() 46 | if err != nil { 47 | return nil 48 | } 49 | info, err := keybase.Get(name) 50 | if err != nil { 51 | return err 52 | } 53 | creator := info.PubKey.Address() 54 | msg, err := BuildCreateAdminMsg(creator, viper.GetString(flagEntityName), viper.GetString(flagEntityType), viper.GetString(flagPubKey)) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | res, err := builder.SignBuildBroadcast(name, msg, c.Cdc) 60 | if err != nil { 61 | return err 62 | } 63 | fmt.Fprintf(os.Stderr, "Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) 64 | return nil 65 | 66 | } 67 | 68 | // BuildCreateAdminMsg makes a new CreateAdminMsg. 69 | func BuildCreateAdminMsg(creator sdk.Address, entityName, entityType, pubKey string) (sdk.Msg, error) { 70 | // parse new account pubkey 71 | pub, err := types.PubKeyFromHexString(pubKey) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return types.NewCreateAdminMsg(creator, pub, entityName, entityType), nil 76 | } 77 | -------------------------------------------------------------------------------- /types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | // List of error codes generated by the application. 10 | // Base SDK reserves 0 ~ 99, app's internal error codes 11 | // start from 1000. 12 | const ( 13 | CodeInvalidAmount sdk.CodeType = 1000 14 | CodeInvalidAddress sdk.CodeType = 1001 15 | CodeInvalidPubKey sdk.CodeType = 1002 16 | CodeInvalidAccount sdk.CodeType = 1003 17 | CodeInvalidEntity sdk.CodeType = 1004 18 | CodeSelfCreate sdk.CodeType = 1005 19 | CodeSelfFreeze sdk.CodeType = 1006 20 | CodeInactiveAccount sdk.CodeType = 1007 21 | CodeWrongSigner sdk.CodeType = 1010 22 | CodeWrongMessageFormat sdk.CodeType = 1100 23 | ) 24 | 25 | // ErrInvalidAmount signals that the asset validation failed. 26 | func ErrInvalidAmount(typ string) sdk.Error { 27 | return sdk.NewError(CodeInvalidAmount, fmt.Sprintf("invalid amount: %s", typ)) 28 | } 29 | 30 | // ErrInvalidAddress signals that an invalid address was passed. 31 | func ErrInvalidAddress(typ string) sdk.Error { 32 | return sdk.NewError(CodeInvalidAddress, fmt.Sprintf("invalid address: %s", typ)) 33 | } 34 | 35 | // ErrInvalidPubKey signals that an invalid public key was passed. 36 | func ErrInvalidPubKey(typ string) sdk.Error { 37 | return sdk.NewError(CodeInvalidPubKey, fmt.Sprintf("invalid pub key: %s", typ)) 38 | } 39 | 40 | // ErrInvalidAccount signals that account types mismatched. 41 | func ErrInvalidAccount(typ string) sdk.Error { 42 | return sdk.NewError(CodeInvalidAccount, fmt.Sprintf("invalid account: %s", typ)) 43 | } 44 | 45 | // ErrInvalidLegalEntity signals that legal entities mismatched. 46 | func ErrInvalidLegalEntity(typ string) sdk.Error { 47 | return sdk.NewError(CodeInvalidEntity, fmt.Sprintf("invalid entity: %s", typ)) 48 | } 49 | 50 | // ErrSelfCreate signals that an admin user attempted to create itself. 51 | func ErrSelfCreate(typ string) sdk.Error { 52 | return sdk.NewError(CodeSelfCreate, fmt.Sprintf( 53 | "why on earth are you trying to create yourself? %s", typ)) 54 | } 55 | 56 | // ErrSelfFreeze signals that an admin user attempted to freeze itself. 57 | func ErrSelfFreeze(typ string) sdk.Error { 58 | return sdk.NewError(CodeSelfFreeze, fmt.Sprintf("self-freeze attempted: %s", typ)) 59 | } 60 | 61 | // ErrInactiveUser signals that an inactive user tried to perform an operation. 62 | func ErrInactiveUser(typ string) sdk.Error { 63 | return sdk.NewError(CodeInactiveAccount, fmt.Sprintf("inactive user: %s", typ)) 64 | } 65 | 66 | // ErrWrongSigner signals that a message carried invalid signatures. 67 | func ErrWrongSigner(typ string) sdk.Error { 68 | return sdk.NewError(CodeWrongSigner, fmt.Sprintf("wrong signer: %s", typ)) 69 | } 70 | 71 | // ErrWrongMsgFormat signals that the message was badly formatted. 72 | func ErrWrongMsgFormat(typ string) sdk.Error { 73 | return sdk.NewError(CodeWrongMessageFormat, fmt.Sprintf("wrong message format: %s", typ)) 74 | } 75 | 76 | // ErrUnauthorized signals that an unauthorized user attempted to perform an operation. 77 | func ErrUnauthorized(typ string) sdk.Error { 78 | return sdk.NewError(sdk.CodeUnauthorized, fmt.Sprintf("unauthorized: %s", typ)) 79 | } 80 | -------------------------------------------------------------------------------- /types/ccy_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCurrencies(t *testing.T) { 10 | allCurrencies := []string{ 11 | "AED", 12 | "AFN", 13 | "ALL", 14 | "AMD", 15 | "ANG", 16 | "AOA", 17 | "ARS", 18 | "AUD", 19 | "AWG", 20 | "AZN", 21 | "BAM", 22 | "BBD", 23 | "BDT", 24 | "BGN", 25 | "BHD", 26 | "BIF", 27 | "BMD", 28 | "BND", 29 | "BOB", 30 | "BOV", 31 | "BRL", 32 | "BSD", 33 | "BTN", 34 | "BWP", 35 | "BYN", 36 | "BYR", 37 | "BZD", 38 | "CAD", 39 | "CDF", 40 | "CHE", 41 | "CHF", 42 | "CHW", 43 | "CLF", 44 | "CLP", 45 | "CNY", 46 | "COP", 47 | "COU", 48 | "CRC", 49 | "CUC", 50 | "CUP", 51 | "CVE", 52 | "CZK", 53 | "DJF", 54 | "DKK", 55 | "DOP", 56 | "DZD", 57 | "EGP", 58 | "ERN", 59 | "ETB", 60 | "EUR", 61 | "FJD", 62 | "FKP", 63 | "GBP", 64 | "GEL", 65 | "GHS", 66 | "GIP", 67 | "GMD", 68 | "GNF", 69 | "GTQ", 70 | "GYD", 71 | "HKD", 72 | "HNL", 73 | "HRK", 74 | "HTG", 75 | "HUF", 76 | "IDR", 77 | "ILS", 78 | "INR", 79 | "IQD", 80 | "IRR", 81 | "ISK", 82 | "JMD", 83 | "JOD", 84 | "JPY", 85 | "KES", 86 | "KGS", 87 | "KHR", 88 | "KMF", 89 | "KPW", 90 | "KRW", 91 | "KWD", 92 | "KYD", 93 | "KZT", 94 | "LAK", 95 | "LBP", 96 | "LKR", 97 | "LRD", 98 | "LSL", 99 | "LYD", 100 | "MAD", 101 | "MDL", 102 | "MGA", 103 | "MKD", 104 | "MMK", 105 | "MNT", 106 | "MOP", 107 | "MRO", 108 | "MUR", 109 | "MVR", 110 | "MWK", 111 | "MXN", 112 | "MXV", 113 | "MYR", 114 | "MZN", 115 | "NAD", 116 | "NGN", 117 | "NIO", 118 | "NOK", 119 | "NPR", 120 | "NZD", 121 | "OMR", 122 | "PAB", 123 | "PEN", 124 | "PGK", 125 | "PHP", 126 | "PKR", 127 | "PLN", 128 | "PYG", 129 | "QAR", 130 | "RON", 131 | "RSD", 132 | "RUB", 133 | "RWF", 134 | "SAR", 135 | "SBD", 136 | "SCR", 137 | "SDG", 138 | "SEK", 139 | "SGD", 140 | "SHP", 141 | "SLL", 142 | "SOS", 143 | "SRD", 144 | "SSP", 145 | "STD", 146 | "SVC", 147 | "SYP", 148 | "SZL", 149 | "THB", 150 | "TJS", 151 | "TMT", 152 | "TND", 153 | "TOP", 154 | "TRY", 155 | "TTD", 156 | "TWD", 157 | "TZS", 158 | "UAH", 159 | "UGX", 160 | "USD", 161 | "USN", 162 | "UYI", 163 | "UYU", 164 | "UZS", 165 | "VEF", 166 | "VND", 167 | "VUV", 168 | "WST", 169 | "XAF", 170 | "XCD", 171 | "XOF", 172 | "XPF", 173 | "YER", 174 | "ZAR", 175 | "ZMW", 176 | "ZWL", 177 | } 178 | got := Currencies() 179 | assert.ElementsMatch(t, got, allCurrencies) 180 | } 181 | 182 | func TestValidateDenom(t *testing.T) { 183 | type args struct { 184 | denom string 185 | } 186 | tests := []struct { 187 | name string 188 | args args 189 | want bool 190 | }{ 191 | {"empty", args{""}, false}, 192 | {"USD", args{"USD"}, true}, 193 | {"wrong", args{"wrong"}, false}, 194 | } 195 | for _, tt := range tests { 196 | t.Run(tt.name, func(t *testing.T) { 197 | if got := ValidateDenom(tt.args.denom); got != tt.want { 198 | t.Errorf("ValidateDenom() = %v, want %v", got, tt.want) 199 | } 200 | }) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /types/entity.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Identifiers for legal entities types. 9 | const ( 10 | EntityClearingHouse = "ch" 11 | EntityGeneralClearingMember = "gcm" 12 | EntityIndividualClearingMember = "icm" 13 | EntityCustodian = "custodian" 14 | ) 15 | 16 | // LegalEntity is the interface that wraps the basic accessor methods 17 | // to set and get entities attributes. 18 | type LegalEntity interface { 19 | LegalEntityName() string 20 | LegalEntityType() string 21 | } 22 | 23 | // BaseLegalEntity defines the properties of a legal entity. 24 | type BaseLegalEntity struct { 25 | EntityName string 26 | EntityType string 27 | } 28 | 29 | // LegalEntityName returns the entity's name. 30 | func (e BaseLegalEntity) LegalEntityName() string { 31 | return e.EntityName 32 | } 33 | 34 | // LegalEntityType returns the entity's type. 35 | func (e BaseLegalEntity) LegalEntityType() string { 36 | return e.EntityType 37 | } 38 | 39 | // IsCustodian returns true if the account's owner entity 40 | // is a custodian; false otherwise. 41 | func IsCustodian(e LegalEntity) bool { 42 | return e.LegalEntityType() == EntityCustodian 43 | } 44 | 45 | // IsClearingHouse returns true if the account's owner entity 46 | // is the clearing house; false otherwise. 47 | func IsClearingHouse(e LegalEntity) bool { 48 | return e.LegalEntityType() == EntityClearingHouse 49 | } 50 | 51 | // IsGeneralClearingMember returns true if the account's owner entity 52 | // is a general clearing member; false otherwise. 53 | func IsGeneralClearingMember(e LegalEntity) bool { 54 | return e.LegalEntityType() == EntityGeneralClearingMember 55 | } 56 | 57 | // IsIndividualClearingMember returns true if the account's owner entity 58 | // is an individual clearing member; false otherwise. 59 | func IsIndividualClearingMember(e LegalEntity) bool { 60 | return e.LegalEntityType() == EntityIndividualClearingMember 61 | } 62 | 63 | // IsMember returns true if the account's owner entity is either 64 | // a general or an individual clearing member; false otherwise. 65 | func IsMember(e LegalEntity) bool { 66 | return IsIndividualClearingMember(e) || IsGeneralClearingMember(e) 67 | } 68 | 69 | // BelongToSameEntity returns true if two accounts 70 | // belong to the same legal entity. 71 | func BelongToSameEntity(e1, e2 LegalEntity) bool { 72 | return (e1.LegalEntityName() == e2.LegalEntityName()) && 73 | (e1.LegalEntityType() == e2.LegalEntityType()) 74 | } 75 | 76 | // ValidateLegalEntity performs basic validation 77 | // on types that implement LegalEntity. 78 | func ValidateLegalEntity(e LegalEntity) error { 79 | if len(strings.TrimSpace(e.LegalEntityName())) == 0 { 80 | return fmt.Errorf("legal entity name must be non-nil") 81 | } 82 | if !sliceContainsString([]string{ 83 | EntityClearingHouse, 84 | EntityGeneralClearingMember, 85 | EntityIndividualClearingMember, 86 | EntityCustodian}, e.LegalEntityType()) { 87 | return fmt.Errorf("legal entity type %q is invalid", e.LegalEntityType()) 88 | } 89 | return nil 90 | } 91 | 92 | func sliceContainsString(slice []string, target string) bool { 93 | for _, s := range slice { 94 | if s == target { 95 | return true 96 | } 97 | } 98 | return false 99 | } 100 | -------------------------------------------------------------------------------- /types/account_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/stretchr/testify/assert" 8 | crypto "github.com/tendermint/go-crypto" 9 | ) 10 | 11 | func TestBelongToSameEntity(t *testing.T) { 12 | acct1, _ := makeAdminUser("ent1", EntityClearingHouse) 13 | acct2, _ := makeUser("ent2", EntityClearingHouse) 14 | acct3, _ := makeAssetAccount(nil, "ent2", EntityClearingHouse) 15 | acct4, _ := makeAssetAccount(nil, "ent2", EntityCustodian) 16 | type args struct { 17 | acct1 *AppAccount 18 | acct2 *AppAccount 19 | } 20 | tests := []struct { 21 | name string 22 | args args 23 | want bool 24 | }{ 25 | {"identity", args{acct1, acct1}, true}, 26 | {"same type, different name", args{acct1, acct2}, false}, 27 | {"different type, same name", args{acct3, acct4}, false}, 28 | {"different account, same entity", args{acct2, acct3}, true}, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | if got := BelongToSameEntity(tt.args.acct1, tt.args.acct2); got != tt.want { 33 | t.Errorf("BelongToSameEntity() = %v, want %v", got, tt.want) 34 | } 35 | }) 36 | } 37 | } 38 | 39 | func Test_sliceContainsString(t *testing.T) { 40 | stringSlice := []string{"xxx", "yyy", "zzz"} 41 | type args struct { 42 | slice []string 43 | target string 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | want bool 49 | }{ 50 | {"nil string", args{stringSlice, ""}, false}, 51 | {"spotted", args{stringSlice, "xxx"}, true}, 52 | {"empty slice", args{[]string{}, "xxx"}, false}, 53 | {"nil slice", args{[]string{}, "xxx"}, false}, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | got := sliceContainsString(tt.args.slice, tt.args.target) 58 | assert.Equal(t, tt.want, got) 59 | }) 60 | } 61 | } 62 | 63 | /* Auxiliary functions. 64 | */ 65 | 66 | func makeUser(entname, typ string) (*AppAccount, crypto.PrivKey) { 67 | priv := crypto.GenPrivKeyEd25519() 68 | return NewOpUser(priv.PubKey(), nil, entname, typ), priv.Wrap() 69 | } 70 | 71 | func makeAdminUser(entname, typ string) (*AppAccount, crypto.PrivKey) { 72 | priv := crypto.GenPrivKeyEd25519() 73 | return NewAdminUser(priv.PubKey(), nil, entname, typ), priv.Wrap() 74 | } 75 | 76 | func makeAssetAccount(cash sdk.Coins, entname, typ string) (*AppAccount, crypto.Address) { 77 | pub := crypto.GenPrivKeyEd25519().PubKey() 78 | return NewAssetAccount(pub, cash, nil, entname, typ), pub.Address() 79 | } 80 | 81 | func TestGetAccountDecoder(t *testing.T) { 82 | cdc := MakeCodec() 83 | decoder := GetAccountDecoder(cdc) 84 | admin, _ := makeAdminUser("member", "gcm") 85 | adminBz, _ := cdc.MarshalBinary(admin) 86 | tests := []struct { 87 | name string 88 | account *AppAccount 89 | bz []byte 90 | wantErr bool 91 | }{ 92 | {"admin", admin, adminBz, false}, 93 | {"nil", &AppAccount{}, []byte{}, true}, 94 | } 95 | for _, tt := range tests { 96 | t.Run(tt.name, func(t *testing.T) { 97 | acct, err := decoder(tt.bz) 98 | assert.Equal(t, tt.wantErr, err != nil) 99 | if err == nil { 100 | concreteAcct := acct.(*AppAccount) 101 | assert.True(t, accountEqual(concreteAcct, tt.account)) 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/cosmos/cosmos-sdk/wire" 8 | "github.com/stretchr/testify/assert" 9 | crypto "github.com/tendermint/go-crypto" 10 | ) 11 | 12 | // ToClearingHouseAdmin verifies that a GenesisAccount is converted correctly into an AppAccount (a Clearing House admin user) 13 | func Test_ToClearingHouseAdmin(t *testing.T) { 14 | cdc := MakeCodec() 15 | wire.RegisterCrypto(cdc) 16 | 17 | pubBytes, _ := hex.DecodeString("01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f874") 18 | publicKey1, _ := crypto.PubKeyFromBytes(pubBytes) 19 | 20 | pubBytes, _ = hex.DecodeString("01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f875") 21 | publicKey2, _ := crypto.PubKeyFromBytes(pubBytes) 22 | 23 | pubBytes, _ = hex.DecodeString("01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f876") 24 | publicKey3, _ := crypto.PubKeyFromBytes(pubBytes) 25 | 26 | adminCreated1 := NewAdminUser(publicKey1, nil, "ClearChain", "ch") 27 | adminCreated2 := NewAdminUser(publicKey2, nil, "ClearingHouse", "ch") 28 | adminCreated3 := NewAdminUser(publicKey3, nil, "Admin", "ch") 29 | 30 | type args struct { 31 | jsonValue string 32 | } 33 | tests := []struct { 34 | name string 35 | args args 36 | expectedAccount *AppAccount 37 | }{ 38 | { 39 | "instantiate admin 1 ok", args{jsonValue: "{\"public_key\":\"01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f874\", \"entity_name\":\"ClearChain\"}"}, adminCreated1, 40 | }, 41 | { 42 | "instantiate admin 2 ok", args{jsonValue: "{\"public_key\":\"01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f875\", \"entity_name\":\"ClearingHouse\"}"}, adminCreated2, 43 | }, 44 | { 45 | "instantiate admin 3 ok", args{jsonValue: "{\"public_key\":\"01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f876\", \"entity_name\":\"Admin\"}"}, adminCreated3, 46 | }, 47 | } 48 | 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | var genesisAcc GenesisAccount 52 | err := cdc.UnmarshalJSON([]byte(tt.args.jsonValue), &genesisAcc) 53 | assert.Nil(t, err) 54 | adminUser, err := genesisAcc.ToClearingHouseAdmin() 55 | assert.Nil(t, err) 56 | assert.Equal(t, hex.EncodeToString(tt.expectedAccount.Address), hex.EncodeToString(adminUser.Address)) 57 | assert.True(t, tt.expectedAccount.PubKey.Equals(adminUser.PubKey)) 58 | assert.True(t, adminUser.Coins.IsZero()) 59 | assert.True(t, adminUser.Creator == nil) 60 | assert.Equal(t, tt.expectedAccount.EntityName, adminUser.EntityName) 61 | assert.Equal(t, tt.expectedAccount.EntityType, adminUser.EntityType) 62 | assert.Equal(t, tt.expectedAccount.AccountType, adminUser.AccountType) 63 | assert.Equal(t, AccountUser, adminUser.AccountType) 64 | assert.Equal(t, tt.expectedAccount.Active, adminUser.Active) 65 | assert.True(t, adminUser.Active) 66 | assert.Equal(t, tt.expectedAccount.Admin, adminUser.Admin) 67 | assert.True(t, adminUser.Admin) 68 | }) 69 | } 70 | } 71 | 72 | func TestPubKeyFromHexString(t *testing.T) { 73 | type args struct { 74 | s string 75 | } 76 | tests := []struct { 77 | name string 78 | args args 79 | wantErr bool 80 | }{ 81 | {"good key", args{"01328eaf5937458f6c39c54ee3624137cabe7af88226454fd30180c8da6c711ad6de7f6053"}, false}, 82 | {"bad key", args{"0124137cabe7af88226454fd30180c8da6c711ad6de7f6053"}, true}, 83 | {"nil", args{""}, true}, 84 | } 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | _, err := PubKeyFromHexString(tt.args.s) 88 | assert.Equal(t, (err != nil), tt.wantErr) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cosmos/cosmos-sdk/baseapp" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/cosmos/cosmos-sdk/wire" 10 | "github.com/cosmos/cosmos-sdk/x/auth" 11 | "github.com/tendermint/abci/server" 12 | abci "github.com/tendermint/abci/types" 13 | "github.com/tendermint/clearchain/types" 14 | cmn "github.com/tendermint/tmlibs/common" 15 | dbm "github.com/tendermint/tmlibs/db" 16 | "github.com/tendermint/tmlibs/log" 17 | ) 18 | 19 | // AppName defines the name of the app. 20 | const AppName = "ClearchainApp" 21 | 22 | // ClearchainApp is basic application 23 | type ClearchainApp struct { 24 | *baseapp.BaseApp 25 | cdc *wire.Codec 26 | capKeyMainStore *sdk.KVStoreKey 27 | capKeyIBCStore *sdk.KVStoreKey 28 | accountMapper sdk.AccountMapper 29 | } 30 | 31 | // NewClearchainApp creates a new ClearchainApp type. 32 | func NewClearchainApp(logger log.Logger, db dbm.DB) *ClearchainApp { 33 | var app = &ClearchainApp{ 34 | BaseApp: baseapp.NewBaseApp(AppName, logger, db), 35 | cdc: types.MakeCodec(), 36 | capKeyMainStore: sdk.NewKVStoreKey("main"), 37 | capKeyIBCStore: sdk.NewKVStoreKey("ibc"), 38 | } 39 | // define the account mapper 40 | app.accountMapper = auth.NewAccountMapperSealed( 41 | app.capKeyMainStore, // target store 42 | &types.AppAccount{}, // prototype 43 | ) 44 | // add handlers and register routes 45 | types.RegisterRoutes(app.Router(), app.accountMapper) 46 | 47 | // initialise BaseApp 48 | app.SetTxDecoder(app.txDecoder) 49 | app.SetInitChainer(app.initChainer) 50 | // Multi-store feature is currently broken 51 | // https://github.com/cosmos/cosmos-sdk/issues/532 52 | app.MountStoresIAVL(app.capKeyMainStore) 53 | app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper)) 54 | err := app.LoadLatestVersion(app.capKeyMainStore) 55 | if err != nil { 56 | cmn.Exit(err.Error()) 57 | } 58 | 59 | return app 60 | } 61 | 62 | // RunForever starts the abci server 63 | func (app *ClearchainApp) RunForever(addrPtr string) { 64 | srv, err := server.NewServer(addrPtr, "socket", app) 65 | if err != nil { 66 | panic(err) 67 | } 68 | srv.Start() 69 | // Wait forever 70 | cmn.TrapSignal(func() { 71 | // Cleanup 72 | srv.Stop() 73 | }) 74 | } 75 | 76 | // custom logic for transaction decoding 77 | func (app *ClearchainApp) txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { 78 | // StdTx.Msg is an interface. The concrete types are registered by MakeCodec. 79 | var tx = sdk.StdTx{} 80 | if len(txBytes) == 0 { 81 | return nil, sdk.ErrTxDecode("txBytes are empty") 82 | } 83 | err := app.cdc.UnmarshalBinary(txBytes, &tx) 84 | if err != nil { 85 | return nil, sdk.ErrTxDecode("").TraceCause(err, "") 86 | } 87 | return tx, nil 88 | } 89 | 90 | // custom logic for clearchain initialization 91 | func (app *ClearchainApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { 92 | stateJSON := req.AppStateBytes 93 | genesisState := new(types.GenesisState) 94 | err := json.Unmarshal(stateJSON, genesisState) 95 | if err != nil { 96 | panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 97 | // return sdk.ErrGenesisParse("").TraceCause(err, "") 98 | } 99 | 100 | genChAdmin := genesisState.ClearingHouseAdmin 101 | if (genChAdmin != types.GenesisAccount{}) { 102 | acc, err := genChAdmin.ToClearingHouseAdmin() 103 | if err != nil { 104 | panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 105 | // return sdk.ErrGenesisParse("").TraceCause(err, "") 106 | } 107 | app.accountMapper.SetAccount(ctx, acc) 108 | fmt.Println("***** Set Ch Admin *****") 109 | fmt.Printf("Entity name: %v \n", acc.EntityName) 110 | fmt.Printf("Entity type: %v \n", acc.EntityType) 111 | fmt.Printf("Public key: %v \n", genChAdmin.PubKeyHexa) 112 | fmt.Println("*****") 113 | } 114 | 115 | fmt.Println("Genesis file loaded successfully!") 116 | return abci.ResponseInitChain{} 117 | } 118 | -------------------------------------------------------------------------------- /cmd/clearchaind/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/spf13/viper" 10 | 11 | "github.com/cosmos/cosmos-sdk/server" 12 | "github.com/spf13/cobra" 13 | abci "github.com/tendermint/abci/types" 14 | "github.com/tendermint/clearchain" 15 | "github.com/tendermint/clearchain/app" 16 | "github.com/tendermint/clearchain/types" 17 | crypto "github.com/tendermint/go-crypto" 18 | "github.com/tendermint/go-crypto/keys" 19 | "github.com/tendermint/go-crypto/keys/words" 20 | "github.com/tendermint/tmlibs/cli" 21 | cmn "github.com/tendermint/tmlibs/common" 22 | dbm "github.com/tendermint/tmlibs/db" 23 | "github.com/tendermint/tmlibs/log" 24 | ) 25 | 26 | const ( 27 | defaultClearingHouseName = "ClearingHouse" 28 | defaultConfigBaseDir = ".clearchaind" 29 | flagClearingHouseName = "clearing-house-name" 30 | ) 31 | 32 | var ( 33 | // clearchaindCmd is the entry point for this binary 34 | clearchaindCmd = &cobra.Command{ 35 | Use: "clearchaind", 36 | Short: "Clearchain Daemon (server)", 37 | } 38 | versionCmd = &cobra.Command{ 39 | Use: "version", 40 | Short: "Print the app version", 41 | Run: doVersionCmd, 42 | } 43 | ) 44 | 45 | func doVersionCmd(cmd *cobra.Command, args []string) { 46 | v := clearchain.Version 47 | if len(v) == 0 { 48 | fmt.Fprintln(os.Stderr, "unset") 49 | return 50 | } 51 | fmt.Fprintln(os.Stderr, v) 52 | } 53 | 54 | func initCommand(logger log.Logger) *cobra.Command { 55 | cmd := server.InitCmd(defaultOptions, logger) 56 | cmd.Flags().String(flagClearingHouseName, defaultClearingHouseName, "Clearing House name") 57 | cmd.Args = cobra.MaximumNArgs(1) 58 | return cmd 59 | } 60 | 61 | func main() { 62 | logger := log.NewTMLogger(log.NewSyncWriter(os.Stderr)).With("module", "main") 63 | initCmd := server.InitCmd(defaultOptions, logger) 64 | initCmd.Flags().String(flagClearingHouseName, defaultClearingHouseName, "Clearing House name") 65 | initCmd.Args = cobra.MaximumNArgs(1) 66 | clearchaindCmd.AddCommand( 67 | initCommand(logger), 68 | server.StartCmd(generateApp, logger), 69 | server.UnsafeResetAllCmd(logger), 70 | server.ShowNodeIdCmd(logger), 71 | server.ShowValidatorCmd(logger), 72 | versionCmd, 73 | ) 74 | // prepare and add flags 75 | rootDir := os.ExpandEnv(defaultConfigBaseDir) 76 | executor := cli.PrepareBaseCmd(clearchaindCmd, "CC", rootDir) 77 | executor.Execute() 78 | } 79 | 80 | func readOrGenerateKey(args []string) (crypto.PubKey, string, error) { 81 | if len(args) != 0 { // user has given a hexadecimal pubkey on the command line 82 | pub, err := types.PubKeyFromHexString(args[0]) 83 | if err != nil { 84 | return crypto.PubKey{}, "", err 85 | } 86 | return pub, "", nil 87 | } 88 | return generateKey() 89 | } 90 | 91 | // defaultOptions sets up the app_options for the 92 | // default genesis file 93 | func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) { 94 | pub, secret, err := readOrGenerateKey(args) 95 | if err != nil { 96 | return nil, "", nil, err 97 | } 98 | opts := fmt.Sprintf(`{ 99 | "ch_admin": { 100 | "public_key": "%s", 101 | "entity_name": "%s" 102 | } 103 | }`, hex.EncodeToString(pub.Bytes()), viper.GetString(flagClearingHouseName)) 104 | return json.RawMessage(opts), secret, pub.Address(), nil 105 | } 106 | 107 | func generateApp(rootDir string, logger log.Logger) (abci.Application, error) { 108 | db, err := dbm.NewGoLevelDB("clearchain", rootDir) 109 | if err != nil { 110 | return nil, err 111 | } 112 | bapp := app.NewClearchainApp(logger, db) 113 | return bapp, nil 114 | } 115 | 116 | func generateKey() (crypto.PubKey, string, error) { 117 | // construct an in-memory key store 118 | codec, err := words.LoadCodec("english") 119 | if err != nil { 120 | return crypto.PubKey{}, "", err 121 | } 122 | keybase := keys.New( 123 | dbm.NewMemDB(), 124 | codec, 125 | ) 126 | 127 | // generate a private key, with recovery phrase 128 | info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519) 129 | if err != nil { 130 | return crypto.PubKey{}, "", err 131 | } 132 | 133 | return info.PubKey, secret, nil 134 | } 135 | -------------------------------------------------------------------------------- /types/account.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/wire" 8 | "github.com/cosmos/cosmos-sdk/x/auth" 9 | "github.com/tendermint/go-crypto" 10 | ) 11 | 12 | // EntityType string identifiers 13 | const ( 14 | AccountUser = "user" 15 | AccountAsset = "asset" 16 | ) 17 | 18 | // ensure AppAccount implements the sdk.Account interface 19 | var ( 20 | _ sdk.Account = (*AppAccount)(nil) 21 | _ LegalEntity = (*AppAccount)(nil) 22 | ) 23 | 24 | // AppAccount defines the properties of an AppAccount. 25 | type AppAccount struct { 26 | auth.BaseAccount 27 | BaseLegalEntity 28 | Creator sdk.Address 29 | AccountType string 30 | Active bool 31 | Admin bool 32 | } 33 | 34 | // NewAppAccount constructs a new account instance. 35 | func newAppAccount(pub crypto.PubKey, cash sdk.Coins, creator sdk.Address, typ string, 36 | isActive bool, isAdmin bool, entityName, entityType string) *AppAccount { 37 | baseaccount := auth.BaseAccount{ 38 | Address: pub.Address(), 39 | PubKey: pub, 40 | Coins: cash, 41 | } 42 | entity := BaseLegalEntity{ 43 | EntityName: entityName, 44 | EntityType: entityType, 45 | } 46 | return &AppAccount{ 47 | BaseAccount: baseaccount, 48 | BaseLegalEntity: entity, 49 | Creator: creator, 50 | AccountType: typ, 51 | Active: isActive, 52 | Admin: isAdmin, 53 | } 54 | } 55 | 56 | // NewOpUser constructs a new account instance, setting cash to nil. 57 | func NewOpUser(pub crypto.PubKey, creator sdk.Address, entityName, entityType string) *AppAccount { 58 | return newAppAccount(pub, nil, creator, AccountUser, true, false, entityName, entityType) 59 | } 60 | 61 | // NewAdminUser constructs a new account instance, setting cash to nil. 62 | func NewAdminUser(pub crypto.PubKey, creator sdk.Address, entityName, entityType string) *AppAccount { 63 | return newAppAccount(pub, nil, creator, AccountUser, true, true, entityName, entityType) 64 | } 65 | 66 | // NewAssetAccount constructs a new account instance. 67 | func NewAssetAccount(pub crypto.PubKey, cash sdk.Coins, creator sdk.Address, entityName, entityType string) *AppAccount { 68 | return newAppAccount(pub, cash, creator, AccountAsset, true, false, entityName, entityType) 69 | } 70 | 71 | // GetAccountType returns the account type. 72 | func (a AppAccount) GetAccountType() string { 73 | return a.AccountType 74 | } 75 | 76 | // IsActive returns true if the account is active; false otherwise. 77 | func (a AppAccount) IsActive() bool { 78 | return a.Active 79 | } 80 | 81 | // IsAdmin returns true if the account is admin; false otherwise. 82 | func (a AppAccount) IsAdmin() bool { 83 | return a.Admin 84 | } 85 | 86 | // IsUser returns true if the account holds user data; false otherwise. 87 | func (a AppAccount) IsUser() bool { 88 | return a.GetAccountType() == AccountUser 89 | } 90 | 91 | // IsAsset returns true if the account holds assets; false otherwise. 92 | func (a AppAccount) IsAsset() bool { 93 | return a.GetAccountType() == AccountAsset 94 | } 95 | 96 | // NewAccountMapper creates an account mapper given a storekey 97 | func NewAccountMapper(capKey sdk.StoreKey) sdk.AccountMapper { 98 | return auth.NewAccountMapperSealed(capKey, &AppAccount{}) 99 | } 100 | 101 | // Get the AccountDecoder function for the custom AppAccount 102 | func GetAccountDecoder(cdc *wire.Codec) sdk.AccountDecoder { 103 | return func(accBytes []byte) (res sdk.Account, err error) { 104 | if len(accBytes) == 0 { 105 | return nil, sdk.ErrTxDecode("accBytes are empty") 106 | } 107 | acct := new(AppAccount) 108 | err = cdc.UnmarshalBinary(accBytes, &acct) 109 | if err != nil { 110 | panic(err) 111 | } 112 | return acct, err 113 | } 114 | } 115 | 116 | /* auxiliary functions */ 117 | 118 | func accountEqual(a1, a2 *AppAccount) bool { 119 | return ((a1.AccountType == a2.AccountType) && 120 | (a1.Admin == a2.Admin) && 121 | (a1.Active == a2.Active) && 122 | bytes.Equal(a1.Address, a2.Address) && 123 | (bytes.Equal(a1.GetPubKey().Bytes(), a2.GetPubKey().Bytes())) && 124 | BelongToSameEntity(a1, a2) && 125 | bytes.Equal(a1.Creator, a2.Creator) && 126 | a1.GetCoins().IsEqual(a2.GetCoins())) 127 | } 128 | -------------------------------------------------------------------------------- /types/ccy.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | // Currencies contains the in-memory database of supported currencies 5 | currencies map[string]ccy 6 | ) 7 | 8 | type ccy struct { 9 | decimalPlaces uint 10 | minimumUnit int64 11 | denom string 12 | } 13 | 14 | func init() { 15 | currencies = make(map[string]ccy) 16 | currencyData := []struct { 17 | d uint 18 | m int64 19 | s string 20 | }{ 21 | {2, 1, "AED"}, 22 | {2, 1, "AFN"}, 23 | {2, 1, "ALL"}, 24 | {2, 1, "AMD"}, 25 | {2, 1, "ANG"}, 26 | {2, 1, "AOA"}, 27 | {2, 1, "ARS"}, 28 | {2, 1, "AUD"}, 29 | {2, 1, "AWG"}, 30 | {2, 1, "AZN"}, 31 | {2, 1, "BAM"}, 32 | {2, 1, "BBD"}, 33 | {2, 1, "BDT"}, 34 | {2, 1, "BGN"}, 35 | {3, 1, "BHD"}, 36 | {0, 1, "BIF"}, 37 | {2, 1, "BMD"}, 38 | {2, 1, "BND"}, 39 | {2, 1, "BOB"}, 40 | {2, 1, "BOV"}, 41 | {2, 1, "BRL"}, 42 | {2, 1, "BSD"}, 43 | {2, 1, "BTN"}, 44 | {2, 1, "BWP"}, 45 | {2, 1, "BYN"}, 46 | {0, 1, "BYR"}, 47 | {2, 1, "BZD"}, 48 | {2, 1, "CAD"}, 49 | {2, 1, "CDF"}, 50 | {2, 1, "CHE"}, 51 | {2, 1, "CHF"}, 52 | {2, 1, "CHW"}, 53 | {4, 1, "CLF"}, 54 | {0, 1, "CLP"}, 55 | {2, 1, "CNY"}, 56 | {2, 1, "COP"}, 57 | {2, 1, "COU"}, 58 | {2, 1, "CRC"}, 59 | {2, 1, "CUC"}, 60 | {2, 1, "CUP"}, 61 | {0, 1, "CVE"}, 62 | {2, 1, "CZK"}, 63 | {0, 1, "DJF"}, 64 | {2, 1, "DKK"}, 65 | {2, 1, "DOP"}, 66 | {2, 1, "DZD"}, 67 | {2, 1, "EGP"}, 68 | {2, 1, "ERN"}, 69 | {2, 1, "ETB"}, 70 | {2, 1, "EUR"}, 71 | {2, 1, "FJD"}, 72 | {2, 1, "FKP"}, 73 | {2, 1, "GBP"}, 74 | {2, 1, "GEL"}, 75 | {2, 1, "GHS"}, 76 | {2, 1, "GIP"}, 77 | {2, 1, "GMD"}, 78 | {0, 1, "GNF"}, 79 | {2, 1, "GTQ"}, 80 | {2, 1, "GYD"}, 81 | {2, 1, "HKD"}, 82 | {2, 1, "HNL"}, 83 | {2, 1, "HRK"}, 84 | {2, 1, "HTG"}, 85 | {2, 1, "HUF"}, 86 | {2, 1, "IDR"}, 87 | {2, 1, "ILS"}, 88 | {2, 1, "INR"}, 89 | {3, 1, "IQD"}, 90 | {2, 1, "IRR"}, 91 | {0, 1, "ISK"}, 92 | {2, 1, "JMD"}, 93 | {3, 1, "JOD"}, 94 | {0, 1, "JPY"}, 95 | {2, 1, "KES"}, 96 | {2, 1, "KGS"}, 97 | {2, 1, "KHR"}, 98 | {0, 1, "KMF"}, 99 | {2, 1, "KPW"}, 100 | {0, 1, "KRW"}, 101 | {3, 1, "KWD"}, 102 | {2, 1, "KYD"}, 103 | {2, 1, "KZT"}, 104 | {2, 1, "LAK"}, 105 | {2, 1, "LBP"}, 106 | {2, 1, "LKR"}, 107 | {2, 1, "LRD"}, 108 | {2, 1, "LSL"}, 109 | {3, 1, "LYD"}, 110 | {2, 1, "MAD"}, 111 | {2, 1, "MDL"}, 112 | {1, 1, "MGA"}, 113 | {2, 1, "MKD"}, 114 | {2, 1, "MMK"}, 115 | {2, 1, "MNT"}, 116 | {2, 1, "MOP"}, 117 | {1, 1, "MRO"}, 118 | {2, 1, "MUR"}, 119 | {2, 1, "MVR"}, 120 | {2, 1, "MWK"}, 121 | {2, 1, "MXN"}, 122 | {2, 1, "MXV"}, 123 | {2, 1, "MYR"}, 124 | {2, 1, "MZN"}, 125 | {2, 1, "NAD"}, 126 | {2, 1, "NGN"}, 127 | {2, 1, "NIO"}, 128 | {2, 1, "NOK"}, 129 | {2, 1, "NPR"}, 130 | {2, 1, "NZD"}, 131 | {3, 1, "OMR"}, 132 | {2, 1, "PAB"}, 133 | {2, 1, "PEN"}, 134 | {2, 1, "PGK"}, 135 | {2, 1, "PHP"}, 136 | {2, 1, "PKR"}, 137 | {2, 1, "PLN"}, 138 | {0, 1, "PYG"}, 139 | {2, 1, "QAR"}, 140 | {2, 1, "RON"}, 141 | {2, 1, "RSD"}, 142 | {2, 1, "RUB"}, 143 | {0, 1, "RWF"}, 144 | {2, 1, "SAR"}, 145 | {2, 1, "SBD"}, 146 | {2, 1, "SCR"}, 147 | {2, 1, "SDG"}, 148 | {2, 1, "SEK"}, 149 | {2, 1, "SGD"}, 150 | {2, 1, "SHP"}, 151 | {2, 1, "SLL"}, 152 | {2, 1, "SOS"}, 153 | {2, 1, "SRD"}, 154 | {2, 1, "SSP"}, 155 | {2, 1, "STD"}, 156 | {2, 1, "SVC"}, 157 | {2, 1, "SYP"}, 158 | {2, 1, "SZL"}, 159 | {2, 1, "THB"}, 160 | {2, 1, "TJS"}, 161 | {2, 1, "TMT"}, 162 | {3, 1, "TND"}, 163 | {2, 1, "TOP"}, 164 | {2, 1, "TRY"}, 165 | {2, 1, "TTD"}, 166 | {2, 1, "TWD"}, 167 | {2, 1, "TZS"}, 168 | {2, 1, "UAH"}, 169 | {0, 1, "UGX"}, 170 | {2, 1, "USD"}, 171 | {2, 1, "USN"}, 172 | {0, 1, "UYI"}, 173 | {2, 1, "UYU"}, 174 | {2, 1, "UZS"}, 175 | {2, 1, "VEF"}, 176 | {0, 1, "VND"}, 177 | {0, 1, "VUV"}, 178 | {2, 1, "WST"}, 179 | {0, 1, "XAF"}, 180 | {2, 1, "XCD"}, 181 | {0, 1, "XOF"}, 182 | {0, 1, "XPF"}, 183 | {2, 1, "YER"}, 184 | {2, 1, "ZAR"}, 185 | {2, 1, "ZMW"}, 186 | {2, 1, "ZWL"}, 187 | } 188 | for _, d := range currencyData { 189 | currencies[d.s] = ccy{decimalPlaces: d.d, minimumUnit: d.m, denom: d.s} 190 | } 191 | } 192 | 193 | func Currencies() (ccys []string) { 194 | for k := range currencies { 195 | ccys = append(ccys, k) 196 | } 197 | return 198 | } 199 | 200 | func ValidateDenom(denom string) bool { 201 | _, ok := currencies[denom] 202 | return ok 203 | } 204 | -------------------------------------------------------------------------------- /app/app_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/cosmos/cosmos-sdk/wire" 14 | 15 | abci "github.com/tendermint/abci/types" 16 | crypto "github.com/tendermint/go-crypto" 17 | common "github.com/tendermint/tmlibs/common" 18 | dbm "github.com/tendermint/tmlibs/db" 19 | "github.com/tendermint/tmlibs/log" 20 | 21 | "github.com/tendermint/clearchain/types" 22 | ) 23 | 24 | // TODO: init state 25 | func TestApp_DepositMsg(t *testing.T) { 26 | 27 | cc := newTestClearchainApp() 28 | junk := []byte("khekfhewgfsug") 29 | cc.BeginBlock(abci.RequestBeginBlock{}) 30 | // send a deposit msg 31 | // garbage in, garbage out 32 | ctx := cc.NewContext(false, abci.Header{}) 33 | chOpAddr, chOpPrivKey := fakeOpAccount(cc, ctx, types.EntityClearingHouse, "CH") 34 | custAssetAddr := fakeAssetAccount(cc, ctx, sdk.Coins{}, types.EntityCustodian, "CUST") 35 | memberAssetAddr := fakeAssetAccount(cc, ctx, sdk.Coins{}, types.EntityIndividualClearingMember, "ICM") 36 | depositMsg := types.DepositMsg{Operator: chOpAddr, Sender: custAssetAddr, 37 | Recipient: memberAssetAddr, Amount: sdk.Coin{"USD", 700}} 38 | depositTx := makeTx(cc.cdc, depositMsg, chOpPrivKey) 39 | dres := cc.DeliverTx(junk) 40 | assert.EqualValues(t, sdk.CodeTxDecode, dres.Code, dres.Log) 41 | // get real working 42 | dres = cc.DeliverTx(depositTx) 43 | assert.EqualValues(t, sdk.CodeOK, dres.Code, dres.Log) 44 | cc.Commit() 45 | cc.EndBlock(abci.RequestEndBlock{}) 46 | 47 | // Query data to verify the deposit 48 | res := cc.Query(abci.RequestQuery{Data: memberAssetAddr, Path: "/main/key"}) 49 | codec := cc.cdc 50 | var foundAcc *types.AppAccount 51 | err := codec.UnmarshalBinary(res.GetValue(), &foundAcc) 52 | assert.Nil(t, err) 53 | assert.NotNil(t, foundAcc) 54 | assert.Equal(t, int64(700), foundAcc.Coins.AmountOf("USD")) 55 | } 56 | 57 | func TestApp_FreezeOperator(t *testing.T) { 58 | cc := newTestClearchainApp() 59 | 60 | cc.BeginBlock(abci.RequestBeginBlock{}) 61 | // send a deposit msg 62 | ctx := cc.NewContext(false, abci.Header{}) 63 | chAdmAddr, chAdmPrivKey := fakeAdminAccount(cc, ctx, types.EntityClearingHouse, "CH") 64 | chOpAddr, chOpPrivKey := fakeOpAccount(cc, ctx, types.EntityClearingHouse, "CH") 65 | custAssetAddr := fakeAssetAccount(cc, ctx, sdk.Coins{}, types.EntityCustodian, "CUST") 66 | memberAssetAddr := fakeAssetAccount(cc, ctx, sdk.Coins{}, types.EntityIndividualClearingMember, "ICM") 67 | depositMsg := types.DepositMsg{Operator: chOpAddr, Sender: custAssetAddr, 68 | Recipient: memberAssetAddr, Amount: sdk.Coin{"USD", 700}} 69 | freezeOpMsg := types.FreezeOperatorMsg{types.BaseFreezeAccountMsg{Admin: chAdmAddr, Target: chOpAddr}} 70 | freezeOperatorTx := makeTx(cc.cdc, freezeOpMsg, chAdmPrivKey) 71 | dres := cc.DeliverTx(freezeOperatorTx) 72 | assert.EqualValues(t, sdk.CodeOK, dres.Code, dres.Log) 73 | depositTx := makeTx(cc.cdc, depositMsg, chOpPrivKey) 74 | // get real working 75 | dres = cc.DeliverTx(depositTx) 76 | assert.EqualValues(t, types.CodeInactiveAccount, dres.Code, dres.Log) 77 | cc.EndBlock(abci.RequestEndBlock{}) 78 | } 79 | 80 | func TestApp_FreezeAdmin(t *testing.T) { 81 | cc := newTestClearchainApp() 82 | 83 | cc.BeginBlock(abci.RequestBeginBlock{}) 84 | // send a deposit msg 85 | ctx := cc.NewContext(false, abci.Header{}) 86 | chAdmAddr, chAdmPrivKey := fakeAdminAccount(cc, ctx, types.EntityClearingHouse, "CH") 87 | memAdmAddr, memAdmPrivKey := fakeAdminAccount(cc, ctx, types.EntityGeneralClearingMember, "GCM") 88 | memOpAddr, _ := fakeOpAccount(cc, ctx, types.EntityGeneralClearingMember, "GCM") 89 | // the CH admin freezes the member admin 90 | freezeAdmMsg := types.FreezeAdminMsg{types.BaseFreezeAccountMsg{Admin: chAdmAddr, Target: memAdmAddr}} 91 | freezeAdmTx := makeTx(cc.cdc, freezeAdmMsg, chAdmPrivKey) 92 | dres := cc.DeliverTx(freezeAdmTx) 93 | assert.EqualValues(t, sdk.CodeOK, dres.Code, dres.Log) 94 | // the member's admin can no longer freeze its own operators 95 | freezeOpMsg := types.FreezeOperatorMsg{types.BaseFreezeAccountMsg{Admin: memAdmAddr, Target: memOpAddr}} 96 | freezeOperatorTx := makeTx(cc.cdc, freezeOpMsg, memAdmPrivKey) 97 | dres = cc.DeliverTx(freezeOperatorTx) 98 | assert.EqualValues(t, types.CodeInactiveAccount, dres.Code, dres.Log) 99 | cc.EndBlock(abci.RequestEndBlock{}) 100 | } 101 | 102 | //Test_Genesis is an end-to-end test that verifies the complete process of loading a genesis file. 103 | // It makes the app read an external genesis file and then verifies that all accounts were created by using the Query interface 104 | func Test_Genesis(t *testing.T) { 105 | 106 | app := newTestClearchainApp() 107 | codec := app.cdc 108 | absPathFileOk, err := filepath.Abs("test/genesis_ok_ch_admin_test.json") 109 | pubBytes, _ := hex.DecodeString("01328eaf59335aa6724f253ca8f1620b249bb83e665d7e5134e9bf92079b2549df3572f874") 110 | publicKey1, _ := crypto.PubKeyFromBytes(pubBytes) 111 | 112 | adminCreated1 := types.NewAdminUser(publicKey1, nil, "ClearChain", "ch") 113 | stateBytes, _ := common.ReadFile(absPathFileOk) 114 | vals := []abci.Validator{} 115 | res := app.Query(abci.RequestQuery{Data: []byte("nothing"), Path: "/main/key"}) 116 | assert.Equal(t, 0, len(res.Value)) 117 | app.BeginBlock(abci.RequestBeginBlock{}) 118 | app.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) 119 | app.Commit() 120 | app.EndBlock(abci.RequestEndBlock{}) 121 | 122 | expAcc := adminCreated1 123 | // Query the existing data 124 | res = app.Query(abci.RequestQuery{Data: expAcc.GetAddress().Bytes(), Path: "/main/key"}) 125 | assert.NotNil(t, res.GetValue()) 126 | var foundAcc *types.AppAccount 127 | err = codec.UnmarshalBinary(res.Value, &foundAcc) 128 | if err != nil { 129 | panic(err) 130 | } 131 | assert.Nil(t, err) 132 | assert.True(t, bytes.Equal(expAcc.Address.Bytes(), foundAcc.Address.Bytes())) 133 | assert.True(t, expAcc.PubKey.Equals(foundAcc.PubKey)) 134 | assert.True(t, foundAcc.Coins.IsZero()) 135 | assert.Equal(t, len(foundAcc.Creator), 0) 136 | assert.Equal(t, expAcc.EntityName, foundAcc.EntityName) 137 | assert.Equal(t, expAcc.EntityType, foundAcc.EntityType) 138 | assert.Equal(t, expAcc.AccountType, foundAcc.AccountType) 139 | assert.Equal(t, types.AccountUser, foundAcc.AccountType) 140 | assert.Equal(t, expAcc.Active, foundAcc.Active) 141 | assert.True(t, foundAcc.Active) 142 | assert.Equal(t, expAcc.Admin, foundAcc.Admin) 143 | assert.True(t, foundAcc.Admin) 144 | } 145 | 146 | func makeTx(cdc *wire.Codec, msg sdk.Msg, keys ...crypto.PrivKey) []byte { 147 | sigs := make([]sdk.StdSignature, len(keys)) 148 | for i, k := range keys { 149 | sig := k.Sign(sdk.StdSignBytes("", []int64{0}, sdk.StdFee{}, msg)) 150 | sigs[i] = sdk.StdSignature{ 151 | PubKey: k.PubKey(), 152 | Signature: sig, 153 | Sequence: 0, 154 | } 155 | } 156 | tx := sdk.NewStdTx(msg, sdk.StdFee{}, sigs) 157 | 158 | bz, err := cdc.MarshalBinary(tx) 159 | if err != nil { 160 | panic(err) 161 | } 162 | 163 | return bz 164 | } 165 | 166 | func fakeAssetAccount(cc *ClearchainApp, ctx sdk.Context, cash sdk.Coins, typ string, entityName string) sdk.Address { 167 | pub := crypto.GenPrivKeyEd25519().PubKey() 168 | addr := pub.Address() 169 | acct := types.NewAssetAccount(pub, cash, nil, entityName, typ) 170 | cc.accountMapper.SetAccount(ctx, acct) 171 | return addr 172 | } 173 | 174 | func fakeOpAccount(cc *ClearchainApp, ctx sdk.Context, typ string, entityName string) (sdk.Address, crypto.PrivKey) { 175 | priv := crypto.GenPrivKeyEd25519() 176 | pub := priv.PubKey() 177 | addr := pub.Address() 178 | acct := types.NewOpUser(pub, []byte(""), entityName, typ) 179 | cc.accountMapper.SetAccount(ctx, acct) 180 | return addr, priv.Wrap() 181 | } 182 | 183 | func fakeAdminAccount(cc *ClearchainApp, ctx sdk.Context, typ string, entityName string) (sdk.Address, crypto.PrivKey) { 184 | priv := crypto.GenPrivKeyEd25519() 185 | pub := priv.PubKey() 186 | addr := pub.Address() 187 | acct := types.NewAdminUser(pub, []byte(""), entityName, typ) 188 | cc.accountMapper.SetAccount(ctx, acct) 189 | return addr, priv.Wrap() 190 | } 191 | 192 | // newTestClearchainApp a ClearchainApp with an in-memory datastore 193 | func newTestClearchainApp() *ClearchainApp { 194 | logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "app") 195 | db := dbm.NewMemDB() 196 | return NewClearchainApp(logger, db) 197 | } 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/bgentry/speakeasy" 6 | packages = ["."] 7 | revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd" 8 | version = "v0.1.0" 9 | 10 | [[projects]] 11 | branch = "master" 12 | name = "github.com/btcsuite/btcd" 13 | packages = ["btcec"] 14 | revision = "2be2f12b358dc57d70b8f501b00be450192efbc3" 15 | 16 | [[projects]] 17 | name = "github.com/cosmos/cosmos-sdk" 18 | packages = [ 19 | "baseapp", 20 | "client", 21 | "client/builder", 22 | "client/keys", 23 | "client/lcd", 24 | "client/rpc", 25 | "client/tx", 26 | "mock", 27 | "server", 28 | "store", 29 | "types", 30 | "version", 31 | "wire", 32 | "x/auth", 33 | "x/auth/commands", 34 | "x/auth/rest", 35 | "x/bank", 36 | "x/bank/commands", 37 | "x/bank/rest", 38 | "x/ibc", 39 | "x/ibc/rest" 40 | ] 41 | revision = "4f0acc4df8de03ac5b90b2173e5c1f73afb0c04e" 42 | version = "v0.13.1" 43 | 44 | [[projects]] 45 | name = "github.com/davecgh/go-spew" 46 | packages = ["spew"] 47 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 48 | version = "v1.1.0" 49 | 50 | [[projects]] 51 | branch = "master" 52 | name = "github.com/ebuchman/fail-test" 53 | packages = ["."] 54 | revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" 55 | 56 | [[projects]] 57 | name = "github.com/fsnotify/fsnotify" 58 | packages = ["."] 59 | revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" 60 | version = "v1.4.7" 61 | 62 | [[projects]] 63 | name = "github.com/go-kit/kit" 64 | packages = [ 65 | "log", 66 | "log/level", 67 | "log/term" 68 | ] 69 | revision = "4dc7be5d2d12881735283bcab7352178e190fc71" 70 | version = "v0.6.0" 71 | 72 | [[projects]] 73 | name = "github.com/go-logfmt/logfmt" 74 | packages = ["."] 75 | revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" 76 | version = "v0.3.0" 77 | 78 | [[projects]] 79 | name = "github.com/go-stack/stack" 80 | packages = ["."] 81 | revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" 82 | version = "v1.7.0" 83 | 84 | [[projects]] 85 | name = "github.com/gogo/protobuf" 86 | packages = [ 87 | "gogoproto", 88 | "jsonpb", 89 | "proto", 90 | "protoc-gen-gogo/descriptor", 91 | "sortkeys", 92 | "types" 93 | ] 94 | revision = "1adfc126b41513cc696b209667c8656ea7aac67c" 95 | version = "v1.0.0" 96 | 97 | [[projects]] 98 | name = "github.com/golang/protobuf" 99 | packages = [ 100 | "proto", 101 | "ptypes", 102 | "ptypes/any", 103 | "ptypes/duration", 104 | "ptypes/timestamp" 105 | ] 106 | revision = "925541529c1fa6821df4e44ce2723319eb2be768" 107 | version = "v1.0.0" 108 | 109 | [[projects]] 110 | branch = "master" 111 | name = "github.com/golang/snappy" 112 | packages = ["."] 113 | revision = "553a641470496b2327abcac10b36396bd98e45c9" 114 | 115 | [[projects]] 116 | name = "github.com/gorilla/context" 117 | packages = ["."] 118 | revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" 119 | version = "v1.1" 120 | 121 | [[projects]] 122 | name = "github.com/gorilla/mux" 123 | packages = ["."] 124 | revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" 125 | version = "v1.6.1" 126 | 127 | [[projects]] 128 | name = "github.com/gorilla/websocket" 129 | packages = ["."] 130 | revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" 131 | version = "v1.2.0" 132 | 133 | [[projects]] 134 | branch = "master" 135 | name = "github.com/hashicorp/hcl" 136 | packages = [ 137 | ".", 138 | "hcl/ast", 139 | "hcl/parser", 140 | "hcl/printer", 141 | "hcl/scanner", 142 | "hcl/strconv", 143 | "hcl/token", 144 | "json/parser", 145 | "json/scanner", 146 | "json/token" 147 | ] 148 | revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda" 149 | 150 | [[projects]] 151 | branch = "master" 152 | name = "github.com/howeyc/crc16" 153 | packages = ["."] 154 | revision = "2b2a61e366a66d3efb279e46176e7291001e0354" 155 | 156 | [[projects]] 157 | name = "github.com/inconshreveable/mousetrap" 158 | packages = ["."] 159 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" 160 | version = "v1.0" 161 | 162 | [[projects]] 163 | branch = "master" 164 | name = "github.com/jmhodges/levigo" 165 | packages = ["."] 166 | revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" 167 | 168 | [[projects]] 169 | branch = "master" 170 | name = "github.com/kr/logfmt" 171 | packages = ["."] 172 | revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" 173 | 174 | [[projects]] 175 | name = "github.com/magiconair/properties" 176 | packages = ["."] 177 | revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" 178 | version = "v1.7.6" 179 | 180 | [[projects]] 181 | name = "github.com/mattn/go-isatty" 182 | packages = ["."] 183 | revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" 184 | version = "v0.0.3" 185 | 186 | [[projects]] 187 | branch = "master" 188 | name = "github.com/mitchellh/mapstructure" 189 | packages = ["."] 190 | revision = "00c29f56e2386353d58c599509e8dc3801b0d716" 191 | 192 | [[projects]] 193 | name = "github.com/pelletier/go-toml" 194 | packages = ["."] 195 | revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" 196 | version = "v1.1.0" 197 | 198 | [[projects]] 199 | name = "github.com/pkg/errors" 200 | packages = ["."] 201 | revision = "645ef00459ed84a119197bfb8d8205042c6df63d" 202 | version = "v0.8.0" 203 | 204 | [[projects]] 205 | name = "github.com/pmezard/go-difflib" 206 | packages = ["difflib"] 207 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 208 | version = "v1.0.0" 209 | 210 | [[projects]] 211 | branch = "master" 212 | name = "github.com/rcrowley/go-metrics" 213 | packages = ["."] 214 | revision = "8732c616f52954686704c8645fe1a9d59e9df7c1" 215 | 216 | [[projects]] 217 | name = "github.com/spf13/afero" 218 | packages = [ 219 | ".", 220 | "mem" 221 | ] 222 | revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" 223 | version = "v1.0.2" 224 | 225 | [[projects]] 226 | name = "github.com/spf13/cast" 227 | packages = ["."] 228 | revision = "8965335b8c7107321228e3e3702cab9832751bac" 229 | version = "v1.2.0" 230 | 231 | [[projects]] 232 | name = "github.com/spf13/cobra" 233 | packages = ["."] 234 | revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4" 235 | version = "v0.0.2" 236 | 237 | [[projects]] 238 | branch = "master" 239 | name = "github.com/spf13/jwalterweatherman" 240 | packages = ["."] 241 | revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" 242 | 243 | [[projects]] 244 | name = "github.com/spf13/pflag" 245 | packages = ["."] 246 | revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" 247 | version = "v1.0.0" 248 | 249 | [[projects]] 250 | name = "github.com/spf13/viper" 251 | packages = ["."] 252 | revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736" 253 | version = "v1.0.2" 254 | 255 | [[projects]] 256 | name = "github.com/stretchr/testify" 257 | packages = [ 258 | "assert", 259 | "require" 260 | ] 261 | revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" 262 | version = "v1.2.1" 263 | 264 | [[projects]] 265 | branch = "master" 266 | name = "github.com/syndtr/goleveldb" 267 | packages = [ 268 | "leveldb", 269 | "leveldb/cache", 270 | "leveldb/comparer", 271 | "leveldb/errors", 272 | "leveldb/filter", 273 | "leveldb/iterator", 274 | "leveldb/journal", 275 | "leveldb/memdb", 276 | "leveldb/opt", 277 | "leveldb/storage", 278 | "leveldb/table", 279 | "leveldb/util" 280 | ] 281 | revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" 282 | 283 | [[projects]] 284 | name = "github.com/tendermint/abci" 285 | packages = [ 286 | "client", 287 | "example/code", 288 | "example/kvstore", 289 | "server", 290 | "types" 291 | ] 292 | revision = "46686763ba8ea595ede16530ed4a40fb38f49f94" 293 | version = "v0.10.2" 294 | 295 | [[projects]] 296 | branch = "master" 297 | name = "github.com/tendermint/ed25519" 298 | packages = [ 299 | ".", 300 | "edwards25519", 301 | "extra25519" 302 | ] 303 | revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" 304 | 305 | [[projects]] 306 | name = "github.com/tendermint/go-crypto" 307 | packages = [ 308 | ".", 309 | "keys", 310 | "keys/bcrypt", 311 | "keys/words", 312 | "keys/words/wordlist" 313 | ] 314 | revision = "c3e19f3ea26f5c3357e0bcbb799b0761ef923755" 315 | version = "v0.5.0" 316 | 317 | [[projects]] 318 | name = "github.com/tendermint/go-wire" 319 | packages = [ 320 | ".", 321 | "data" 322 | ] 323 | revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c" 324 | source = "github.com/tendermint/go-amino" 325 | version = "v0.7.3" 326 | 327 | [[projects]] 328 | name = "github.com/tendermint/iavl" 329 | packages = ["."] 330 | revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787" 331 | version = "v0.7.0" 332 | 333 | [[projects]] 334 | name = "github.com/tendermint/tendermint" 335 | packages = [ 336 | "blockchain", 337 | "cmd/tendermint/commands", 338 | "config", 339 | "consensus", 340 | "consensus/types", 341 | "evidence", 342 | "lite", 343 | "lite/client", 344 | "lite/errors", 345 | "lite/files", 346 | "lite/proxy", 347 | "mempool", 348 | "node", 349 | "p2p", 350 | "p2p/conn", 351 | "p2p/pex", 352 | "p2p/trust", 353 | "p2p/upnp", 354 | "proxy", 355 | "rpc/client", 356 | "rpc/core", 357 | "rpc/core/types", 358 | "rpc/grpc", 359 | "rpc/lib", 360 | "rpc/lib/client", 361 | "rpc/lib/server", 362 | "rpc/lib/types", 363 | "state", 364 | "state/txindex", 365 | "state/txindex/kv", 366 | "state/txindex/null", 367 | "types", 368 | "types/priv_validator", 369 | "version", 370 | "wire" 371 | ] 372 | revision = "6f9956990c444d53f62f2a3905ed410cfe9afe77" 373 | version = "v0.17.1" 374 | 375 | [[projects]] 376 | name = "github.com/tendermint/tmlibs" 377 | packages = [ 378 | "autofile", 379 | "cli", 380 | "cli/flags", 381 | "clist", 382 | "common", 383 | "db", 384 | "flowrate", 385 | "log", 386 | "merkle", 387 | "pubsub", 388 | "pubsub/query" 389 | ] 390 | revision = "24da7009c3d8c019b40ba4287495749e3160caca" 391 | version = "v0.7.1" 392 | 393 | [[projects]] 394 | branch = "master" 395 | name = "golang.org/x/crypto" 396 | packages = [ 397 | "blowfish", 398 | "curve25519", 399 | "nacl/box", 400 | "nacl/secretbox", 401 | "openpgp/armor", 402 | "openpgp/errors", 403 | "poly1305", 404 | "ripemd160", 405 | "salsa20/salsa" 406 | ] 407 | revision = "12892e8c234f4fe6f6803f052061de9057903bb2" 408 | 409 | [[projects]] 410 | branch = "master" 411 | name = "golang.org/x/net" 412 | packages = [ 413 | "context", 414 | "http2", 415 | "http2/hpack", 416 | "idna", 417 | "internal/timeseries", 418 | "lex/httplex", 419 | "trace" 420 | ] 421 | revision = "b68f30494add4df6bd8ef5e82803f308e7f7c59c" 422 | 423 | [[projects]] 424 | branch = "master" 425 | name = "golang.org/x/sys" 426 | packages = ["unix"] 427 | revision = "378d26f46672a356c46195c28f61bdb4c0a781dd" 428 | 429 | [[projects]] 430 | name = "golang.org/x/text" 431 | packages = [ 432 | "collate", 433 | "collate/build", 434 | "internal/colltab", 435 | "internal/gen", 436 | "internal/tag", 437 | "internal/triegen", 438 | "internal/ucd", 439 | "language", 440 | "secure/bidirule", 441 | "transform", 442 | "unicode/bidi", 443 | "unicode/cldr", 444 | "unicode/norm", 445 | "unicode/rangetable" 446 | ] 447 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 448 | version = "v0.3.0" 449 | 450 | [[projects]] 451 | branch = "master" 452 | name = "google.golang.org/genproto" 453 | packages = ["googleapis/rpc/status"] 454 | revision = "ab0870e398d5dd054b868c0db1481ab029b9a9f2" 455 | 456 | [[projects]] 457 | name = "google.golang.org/grpc" 458 | packages = [ 459 | ".", 460 | "balancer", 461 | "codes", 462 | "connectivity", 463 | "credentials", 464 | "grpclb/grpc_lb_v1/messages", 465 | "grpclog", 466 | "internal", 467 | "keepalive", 468 | "metadata", 469 | "naming", 470 | "peer", 471 | "resolver", 472 | "stats", 473 | "status", 474 | "tap", 475 | "transport" 476 | ] 477 | revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" 478 | version = "v1.7.5" 479 | 480 | [[projects]] 481 | name = "gopkg.in/yaml.v2" 482 | packages = ["."] 483 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 484 | version = "v2.2.1" 485 | 486 | [solve-meta] 487 | analyzer-name = "dep" 488 | analyzer-version = 1 489 | inputs-digest = "ea35f0b16bef29139eefd53b530a852b1d8e0d6f84b3111ec6fca26761a282aa" 490 | solver-name = "gps-cdcl" 491 | solver-version = 1 492 | -------------------------------------------------------------------------------- /types/msgs.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | crypto "github.com/tendermint/go-crypto" 10 | ) 11 | 12 | // Defines all the messages (requests) supported by the app 13 | 14 | // message types definitions 15 | const ( 16 | DepositType = "deposit" 17 | SettlementType = "settlement" 18 | WithdrawType = "withdraw" 19 | CreateOperatorType = "createOperator" 20 | CreateAdminType = "createAdmin" 21 | CreateAssetAccountType = "createAsset" 22 | FreezeOperatorType = "freezeOperator" 23 | FreezeAdminType = "freezeAdmin" 24 | ) 25 | 26 | const ( 27 | // AddressLength represents the number of bytes that compose addresses. 28 | AddressLength = 20 29 | ) 30 | 31 | // DepositMsg defines the properties of an asset transfer 32 | type DepositMsg struct { 33 | Operator sdk.Address 34 | Sender sdk.Address 35 | Recipient sdk.Address 36 | Amount sdk.Coin 37 | } 38 | 39 | // ensure DepositMsg implements the sdk.Msg interface 40 | var _ sdk.Msg = DepositMsg{} 41 | var _ sdk.Msg = (*DepositMsg)(nil) 42 | 43 | // ValidateBasic is called by the SDK automatically. 44 | func (msg DepositMsg) ValidateBasic() sdk.Error { 45 | if msg.Amount.Amount <= 0 { 46 | return ErrInvalidAmount("negative amount") 47 | } 48 | if msg.Amount.Denom == "" { 49 | return ErrInvalidAmount("empty denom") 50 | } 51 | if err := validateAddress(msg.Operator); err != nil { 52 | return err 53 | } 54 | if err := validateAddress(msg.Sender); err != nil { 55 | return err 56 | } 57 | if err := validateAddress(msg.Recipient); err != nil { 58 | return err 59 | } 60 | if bytes.Equal(msg.Sender, msg.Recipient) { 61 | return ErrInvalidAddress("sender and recipient have the same address") 62 | } 63 | return nil 64 | } 65 | 66 | // Type returns the message type. 67 | // Must be alphanumeric or empty. 68 | func (msg DepositMsg) Type() string { return DepositType } 69 | 70 | // Get some property of the Msg. 71 | func (msg DepositMsg) Get(key interface{}) (value interface{}) { return nil } 72 | 73 | // GetSignBytes returns the canonical byte representation of the Msg. 74 | func (msg DepositMsg) GetSignBytes() []byte { 75 | bz, err := json.Marshal(msg) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return bz 80 | } 81 | 82 | // GetSigners returns the addrs of signers that must sign. 83 | // CONTRACT: All signatures must be present to be valid. 84 | // CONTRACT: Returns addrs in some deterministic order. 85 | func (msg DepositMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Operator} } 86 | 87 | // SettleMsg defines the properties of a settle transaction. 88 | type SettleMsg struct { 89 | Operator sdk.Address 90 | Sender sdk.Address 91 | Recipient sdk.Address 92 | Amount sdk.Coin 93 | } 94 | 95 | var _ sdk.Msg = SettleMsg{} 96 | 97 | // ValidateBasic is called by the SDK automatically. 98 | func (msg SettleMsg) ValidateBasic() sdk.Error { 99 | // amount may be negative 100 | if msg.Amount.Amount == 0 { 101 | return ErrInvalidAmount("empty or 0 amount not allowed") 102 | } 103 | if msg.Amount.Denom == "" { 104 | return ErrInvalidAmount("empty denom") 105 | } 106 | if err := validateAddress(msg.Operator); err != nil { 107 | return err 108 | } 109 | if err := validateAddress(msg.Sender); err != nil { 110 | return err 111 | } 112 | if err := validateAddress(msg.Recipient); err != nil { 113 | return err 114 | } 115 | if bytes.Equal(msg.Sender, msg.Recipient) { 116 | return ErrInvalidAddress("sender and recipient have the same address") 117 | } 118 | return nil 119 | } 120 | 121 | // Type returns the message type. 122 | // Must be alphanumeric or empty. 123 | func (msg SettleMsg) Type() string { return SettlementType } 124 | 125 | // Get some property of the Msg. 126 | func (msg SettleMsg) Get(key interface{}) (value interface{}) { return nil } 127 | 128 | // GetSignBytes returns the canonical byte representation of the Msg. 129 | func (msg SettleMsg) GetSignBytes() []byte { 130 | bz, err := json.Marshal(msg) 131 | if err != nil { 132 | panic(err) 133 | } 134 | return bz 135 | } 136 | 137 | // GetSigners returns the addrs of signers that must sign. 138 | // CONTRACT: All signatures must be present to be valid. 139 | // CONTRACT: Returns addrs in some deterministic order. 140 | func (msg SettleMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Operator} } 141 | 142 | // WithdrawMsg defines the properties of a withdraw transaction. 143 | type WithdrawMsg struct { 144 | Operator sdk.Address 145 | Sender sdk.Address 146 | Recipient sdk.Address 147 | Amount sdk.Coin 148 | } 149 | 150 | var _ sdk.Msg = WithdrawMsg{} 151 | 152 | // ValidateBasic is called by the SDK automatically. 153 | func (msg WithdrawMsg) ValidateBasic() sdk.Error { 154 | if msg.Amount.Amount <= 0 { 155 | return ErrInvalidAmount("negative or 0 amount not allowed") 156 | } 157 | if msg.Amount.Denom == "" { 158 | return ErrInvalidAmount("empty denom") 159 | } 160 | if err := validateAddress(msg.Operator); err != nil { 161 | return err 162 | } 163 | if err := validateAddress(msg.Sender); err != nil { 164 | return err 165 | } 166 | if err := validateAddress(msg.Recipient); err != nil { 167 | return err 168 | } 169 | if bytes.Equal(msg.Sender, msg.Recipient) { 170 | return ErrInvalidAddress("sender and recipient have the same address") 171 | } 172 | return nil 173 | } 174 | 175 | // Type returns the message type. 176 | // Must be alphanumeric or empty. 177 | func (msg WithdrawMsg) Type() string { return WithdrawType } 178 | 179 | // Get some property of the Msg. 180 | func (msg WithdrawMsg) Get(key interface{}) (value interface{}) { return nil } 181 | 182 | // GetSignBytes returns the canonical byte representation of the Msg. 183 | func (msg WithdrawMsg) GetSignBytes() []byte { 184 | bz, err := json.Marshal(msg) 185 | if err != nil { 186 | panic(err) 187 | } 188 | return bz 189 | } 190 | 191 | // GetSigners returns the addrs of signers that must sign. 192 | // CONTRACT: All signatures must be present to be valid. 193 | // CONTRACT: Returns addrs in some deterministic order. 194 | func (msg WithdrawMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Operator} } 195 | 196 | // CreateAssetAccountMsg defines the property of a create user transaction. 197 | type CreateAssetAccountMsg struct { 198 | Creator sdk.Address 199 | PubKey crypto.PubKey 200 | } 201 | 202 | var _ sdk.Msg = CreateAssetAccountMsg{} 203 | 204 | // ValidateBasic performs basic validation checks and it's 205 | // called by the SDK automatically. 206 | func (msg CreateAssetAccountMsg) ValidateBasic() sdk.Error { 207 | if err := validateAddress(msg.Creator); err != nil { 208 | return err 209 | } 210 | if msg.PubKey.Empty() { 211 | return ErrInvalidPubKey("pub key is nil") 212 | } 213 | if bytes.Equal(msg.Creator, msg.PubKey.Address()) { 214 | return ErrInvalidPubKey("creator and new account have the same address") 215 | } 216 | return nil 217 | } 218 | 219 | // Type returns the message type. 220 | // Must be alphanumeric or empty. 221 | func (msg CreateAssetAccountMsg) Type() string { return CreateAssetAccountType } 222 | 223 | // Get returns some property of the Msg. 224 | func (msg CreateAssetAccountMsg) Get(key interface{}) (value interface{}) { return nil } 225 | 226 | // GetSignBytes returns the canonical byte representation of the Msg. 227 | func (msg CreateAssetAccountMsg) GetSignBytes() []byte { 228 | bz, err := json.Marshal(msg) 229 | if err != nil { 230 | panic(err) 231 | } 232 | return bz 233 | } 234 | 235 | // GetSigners returns the addrs of signers that must sign. 236 | // CONTRACT: All signatures must be present to be valid. 237 | // CONTRACT: Returns addrs in some deterministic order. 238 | func (msg CreateAssetAccountMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Creator} } 239 | 240 | // BaseCreateUserMsg defines the properties of a transaction 241 | // that triggers the creation of a new generic user. 242 | // Legal entitiy is inherited from the creator. 243 | type BaseCreateUserMsg struct { 244 | Creator sdk.Address 245 | PubKey crypto.PubKey 246 | } 247 | 248 | // ValidateBasic is called by the SDK automatically. 249 | func (msg BaseCreateUserMsg) ValidateBasic() sdk.Error { 250 | if msg.PubKey.Empty() { 251 | return ErrInvalidPubKey("pub key is nil") 252 | } 253 | if err := validateAddress(msg.Creator); err != nil { 254 | return err 255 | } 256 | if bytes.Equal(msg.Creator, msg.PubKey.Address()) { 257 | return ErrSelfCreate(fmt.Sprintf("%v", msg.Creator)) 258 | } 259 | return nil 260 | } 261 | 262 | // Type returns the message type. 263 | // Must be alphanumeric or empty. 264 | //func (msg BaseCreateUserMsg) Type() string { return CreateOperatorType } 265 | 266 | // Get returns some property of the Msg. 267 | func (msg BaseCreateUserMsg) Get(key interface{}) (value interface{}) { return nil } 268 | 269 | // GetSignBytes returns the canonical byte representation of the Msg. 270 | func (msg BaseCreateUserMsg) GetSignBytes() []byte { 271 | bz, err := json.Marshal(msg) 272 | if err != nil { 273 | panic(err) 274 | } 275 | return bz 276 | } 277 | 278 | // GetSigners returns the addrs of signers that must sign. 279 | // CONTRACT: All signatures must be present to be valid. 280 | // CONTRACT: Returns addrs in some deterministic order. 281 | func (msg BaseCreateUserMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Creator} } 282 | 283 | // CreateOperatorMsg defines the properties of a transaction 284 | // that triggers the creation of a new unprivileged user. 285 | // Legal entitiy is inherited from the creator. 286 | type CreateOperatorMsg struct{ BaseCreateUserMsg } 287 | 288 | var _ sdk.Msg = (*CreateOperatorMsg)(nil) 289 | 290 | // Type returns the message type. 291 | // Must be alphanumeric or empty. 292 | func (msg CreateOperatorMsg) Type() string { return CreateOperatorType } 293 | 294 | // CreateAdminMsg defines the properties of a transaction 295 | // that triggers the cross-entity creation of privileged users 296 | // The message must carry the legal entity. 297 | // Only a clearing house can utilise this endpoint. 298 | type CreateAdminMsg struct { 299 | BaseCreateUserMsg 300 | BaseLegalEntity 301 | } 302 | 303 | var _ sdk.Msg = (*CreateAdminMsg)(nil) 304 | 305 | // ValidateBasic is called by the SDK automatically. 306 | func (msg CreateAdminMsg) ValidateBasic() sdk.Error { 307 | if err := msg.BaseCreateUserMsg.ValidateBasic(); err != nil { 308 | return err 309 | } 310 | if err := ValidateLegalEntity(msg.BaseLegalEntity); err != nil { 311 | return ErrInvalidLegalEntity(err.Error()) 312 | } 313 | return nil 314 | } 315 | 316 | // Type returns the message type. 317 | // Must be alphanumeric or empty. 318 | func (msg CreateAdminMsg) Type() string { return CreateAdminType } 319 | 320 | // BaseFreezeAccountMsg defines the properties of a transaction 321 | // that freezes user or asset accounts. 322 | type BaseFreezeAccountMsg struct { 323 | Admin sdk.Address 324 | Target sdk.Address 325 | } 326 | 327 | // ValidateBasic is called by the SDK automatically. 328 | func (msg BaseFreezeAccountMsg) ValidateBasic() sdk.Error { 329 | if err := validateAddress(msg.Admin); err != nil { 330 | return err 331 | } 332 | if err := validateAddress(msg.Target); err != nil { 333 | return err 334 | } 335 | if bytes.Equal(msg.Admin, msg.Target) { 336 | return ErrSelfFreeze(fmt.Sprintf("%v", msg.Admin)) 337 | } 338 | return nil 339 | } 340 | 341 | // Type returns the message type. 342 | // Must be alphanumeric or empty. 343 | 344 | // Get returns some property of the Msg. 345 | func (msg BaseFreezeAccountMsg) Get(key interface{}) (value interface{}) { return nil } 346 | 347 | // GetSignBytes returns the canonical byte representation of the Msg. 348 | func (msg BaseFreezeAccountMsg) GetSignBytes() []byte { 349 | bz, err := json.Marshal(msg) 350 | if err != nil { 351 | panic(err) 352 | } 353 | return bz 354 | } 355 | 356 | // GetSigners returns the addrs of signers that must sign. 357 | // CONTRACT: All signatures must be present to be valid. 358 | // CONTRACT: Returns addrs in some deterministic order. 359 | func (msg BaseFreezeAccountMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Admin} } 360 | 361 | // FreezeOperatorMsg defines the properties of a transaction 362 | // that freezes an operator. Admin accounts can freeze their 363 | // own legal entity's operators. 364 | type FreezeOperatorMsg struct{ BaseFreezeAccountMsg } 365 | 366 | var _ sdk.Msg = (*FreezeOperatorMsg)(nil) 367 | 368 | // Type returns the message type. 369 | // Must be alphanumeric or empty. 370 | func (msg FreezeOperatorMsg) Type() string { return FreezeOperatorType } 371 | 372 | // FreezeAdminMsg defines the properties of a transaction 373 | // that freezes an admin. Only clearing house Admin accounts 374 | // can freeze other Admin accounts, regardless of the entity 375 | // that own them. 376 | type FreezeAdminMsg struct{ BaseFreezeAccountMsg } 377 | 378 | var _ sdk.Msg = (*FreezeAdminMsg)(nil) 379 | 380 | // Type returns the message type. 381 | // Must be alphanumeric or empty. 382 | func (msg FreezeAdminMsg) Type() string { return FreezeAdminType } 383 | 384 | /* Constructors */ 385 | 386 | // NewCreateAdminMsg creates a new CreateAdminMsg. 387 | func NewCreateAdminMsg(creator sdk.Address, pubkey crypto.PubKey, 388 | entityName, entityType string) (msg CreateAdminMsg) { 389 | msg.BaseCreateUserMsg.Creator = creator 390 | msg.BaseCreateUserMsg.PubKey = pubkey 391 | msg.BaseLegalEntity.EntityName = entityName 392 | msg.BaseLegalEntity.EntityType = entityType 393 | return 394 | } 395 | 396 | // NewCreateOperatorMsg creates a new CreateOperatorMsg. 397 | func NewCreateOperatorMsg(creator sdk.Address, pubkey crypto.PubKey) (msg CreateOperatorMsg) { 398 | msg.BaseCreateUserMsg.Creator = creator 399 | msg.BaseCreateUserMsg.PubKey = pubkey 400 | return 401 | } 402 | 403 | // NewCreateAssetAccountMsg creates a new CreateAssetAccountMsg. 404 | func NewCreateAssetAccountMsg(creator sdk.Address, pubkey crypto.PubKey) (msg CreateAssetAccountMsg) { 405 | msg.Creator = creator 406 | msg.PubKey = pubkey 407 | return 408 | } 409 | 410 | /* Auxiliary functions, could be undocumented */ 411 | 412 | func validateAddress(addr sdk.Address) sdk.Error { 413 | if addr == nil { 414 | return ErrInvalidAddress("address is nil") 415 | } 416 | if len(addr) != AddressLength { 417 | return ErrInvalidAddress("invalid address length") 418 | } 419 | return nil 420 | } 421 | -------------------------------------------------------------------------------- /types/handler.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/baseapp" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | crypto "github.com/tendermint/go-crypto" 9 | ) 10 | 11 | // RegisterRoutes routes the message (request) to a proper handler. 12 | func RegisterRoutes(r baseapp.Router, accts sdk.AccountMapper) { 13 | r.AddRoute(DepositType, DepositMsgHandler(accts)). 14 | AddRoute(SettlementType, SettleMsgHandler(accts)). 15 | AddRoute(WithdrawType, WithdrawMsgHandler(accts)). 16 | AddRoute(CreateOperatorType, CreateOperatorMsgHandler(accts)). 17 | AddRoute(CreateAdminType, CreateAdminMsgHandler(accts)). 18 | AddRoute(CreateAssetAccountType, CreateAssetAccountMsgHandler(accts)). 19 | AddRoute(FreezeOperatorType, FreezeOperatorMsgHandler(accts)). 20 | AddRoute(FreezeAdminType, FreezeAdminMsgHandler(accts)) 21 | } 22 | 23 | /* 24 | 25 | Deposit functionality. 26 | 27 | Sender is Custodian 28 | Rec is Member 29 | */ 30 | func DepositMsgHandler(accts sdk.AccountMapper) sdk.Handler { 31 | return depositMsgHandler{accts}.Do 32 | } 33 | 34 | type depositMsgHandler struct{ accts sdk.AccountMapper } 35 | 36 | // Deposit logic 37 | func (d depositMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 38 | // ensure proper message 39 | dm, ok := msg.(DepositMsg) 40 | if !ok { 41 | return ErrWrongMsgFormat("expected DepositMsg").Result() 42 | } 43 | // ensure proper types 44 | if _, err := getCHActiveOperator(ctx, d.accts, dm.Operator); err != nil { 45 | return err.Result() 46 | } 47 | sender, err := getActiveAssetWithEntityType(ctx, d.accts, dm.Sender, IsCustodian) 48 | if err != nil { 49 | return err.Result() 50 | } 51 | rcpt, err := getActiveAssetWithEntityType(ctx, d.accts, dm.Recipient, IsMember) 52 | if err != nil { 53 | return err.Result() 54 | } 55 | // Exchange cash 56 | if err := moveMoney(d.accts, ctx, sender, rcpt, dm.Amount, false, true); err != nil { 57 | return err.Result() 58 | } 59 | return sdk.Result{} 60 | } 61 | 62 | /* 63 | Settlement funcionality. 64 | 65 | Operator is CH 66 | Sender is CH 67 | Rec is member 68 | */ 69 | func SettleMsgHandler(accts sdk.AccountMapper) sdk.Handler { return settleMsgHandler{accts}.Do } 70 | 71 | type settleMsgHandler struct{ accts sdk.AccountMapper } 72 | 73 | // Settlement logic 74 | func (sh settleMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 75 | //ensure proper message 76 | sm, ok := msg.(SettleMsg) 77 | if !ok { 78 | return ErrWrongMsgFormat("expected SettleMsg").Result() 79 | } 80 | // ensure proper types 81 | operator, err := getCHActiveOperator(ctx, sh.accts, sm.Operator) 82 | if err != nil { 83 | return err.Result() 84 | } 85 | sender, err := getActiveAssetWithEntityType(ctx, sh.accts, sm.Sender, IsClearingHouse) 86 | if err != nil { 87 | return err.Result() 88 | } 89 | if !BelongToSameEntity(operator, sender) { 90 | return ErrWrongSigner("operator and sender must belong to the same entity").Result() 91 | } 92 | rcpt, err := getActiveAssetWithEntityType(ctx, sh.accts, sm.Recipient, IsMember) 93 | if err != nil { 94 | return err.Result() 95 | } 96 | if err := moveMoney(sh.accts, ctx, sender, rcpt, sm.Amount, false, true); err != nil { 97 | return err.Result() 98 | } 99 | return sdk.Result{} 100 | } 101 | 102 | // WithdrawMsgHandler implements the withdraw functionality. 103 | // 104 | // Sender is member 105 | // Reci is custodian 106 | // Operator is CH 107 | // 108 | func WithdrawMsgHandler(accts sdk.AccountMapper) sdk.Handler { return withdrawMsgHandler{accts}.Do } 109 | 110 | type withdrawMsgHandler struct { 111 | accts sdk.AccountMapper 112 | } 113 | 114 | // Withdraw logic 115 | func (wh withdrawMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 116 | // ensure proper message 117 | wm, ok := msg.(WithdrawMsg) 118 | if !ok { 119 | return ErrWrongMsgFormat("expected WithdrawMsg").Result() 120 | } 121 | // ensure proper types 122 | _, err := getCHActiveOperator(ctx, wh.accts, wm.Operator) 123 | if err != nil { 124 | return err.Result() 125 | } 126 | sender, err := getActiveAssetWithEntityType(ctx, wh.accts, wm.Sender, IsMember) 127 | if err != nil { 128 | return err.Result() 129 | } 130 | rcpt, err := getActiveAssetWithEntityType(ctx, wh.accts, wm.Recipient, IsCustodian) 131 | if err != nil { 132 | return err.Result() 133 | } 134 | err = moveMoney(wh.accts, ctx, sender, rcpt, wm.Amount, true, false) 135 | if err != nil { 136 | return err.Result() 137 | } 138 | return sdk.Result{} 139 | 140 | } 141 | 142 | // CreateOperatorMsgHandler returns the handler's method. 143 | func CreateOperatorMsgHandler(accts sdk.AccountMapper) sdk.Handler { 144 | return createOperatorMsgHandler{accts}.Do 145 | } 146 | 147 | type createOperatorMsgHandler struct{ accts sdk.AccountMapper } 148 | 149 | func (h createOperatorMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 150 | // ensure proper message 151 | cm, ok := msg.(CreateOperatorMsg) 152 | if !ok { 153 | return ErrWrongMsgFormat("expected CreateOperatorMsg").Result() 154 | } 155 | newAcct, err := validateAdminAndCreateOperator(ctx, h.accts, cm.Creator, cm.PubKey) 156 | if err != nil { 157 | return err.Result() 158 | } 159 | h.accts.SetAccount(ctx, newAcct) 160 | return sdk.Result{} 161 | } 162 | 163 | // CreateAdminMsgHandler returns the handler's method. 164 | func CreateAdminMsgHandler(accts sdk.AccountMapper) sdk.Handler { 165 | return createAdminMsgHandler{accts}.Do 166 | } 167 | 168 | type createAdminMsgHandler struct{ accts sdk.AccountMapper } 169 | 170 | func (h createAdminMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 171 | // ensure proper message 172 | cm, ok := msg.(CreateAdminMsg) 173 | if !ok { 174 | return ErrWrongMsgFormat("expected CreateAdminMsg").Result() 175 | } 176 | newAcct, err := validateCHAdminAndCreateXEntityAdmin(ctx, h.accts, cm.Creator, cm.PubKey, cm.BaseLegalEntity) 177 | if err != nil { 178 | return err.Result() 179 | } 180 | h.accts.SetAccount(ctx, newAcct) 181 | return sdk.Result{} 182 | } 183 | 184 | // CreateAssetAccountMsgHandler returns the handler's method. 185 | func CreateAssetAccountMsgHandler(accts sdk.AccountMapper) sdk.Handler { 186 | return createAssetAccountMsgHandler{accts}.Do 187 | } 188 | 189 | type createAssetAccountMsgHandler struct{ accts sdk.AccountMapper } 190 | 191 | // Create asset account logic. 192 | // Admins can create asset accounts for their own entity only. 193 | // TODO: clarify business rules and ensure this is desired 194 | func (h createAssetAccountMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 195 | // ensure proper message 196 | cm, ok := msg.(CreateAssetAccountMsg) 197 | if !ok { 198 | return ErrWrongMsgFormat("expected CreateAssetAccountMsg").Result() 199 | } 200 | // ensure creator exists 201 | // no need for type checking, CreateAssetAccount 202 | // validates types too. 203 | creator, err := getActiveAdmin(ctx, h.accts, cm.Creator) 204 | if err != nil { 205 | return err.Result() 206 | } 207 | // ensure new account does not exist 208 | if h.accts.GetAccount(ctx, cm.PubKey.Address()) != nil { 209 | return ErrInvalidAccount("the account already exists").Result() 210 | } 211 | // Construct a new account 212 | newAcct := NewAssetAccount(cm.PubKey, sdk.Coins{}, creator.Address, creator.LegalEntityName(), creator.LegalEntityType()) 213 | h.accts.SetAccount(ctx, newAcct) 214 | return sdk.Result{} 215 | } 216 | 217 | // FreezeOperatorMsgHandler returns the handler's method. 218 | func FreezeOperatorMsgHandler(accts sdk.AccountMapper) sdk.Handler { 219 | return freezeOperatorMsgHandler{accts}.Do 220 | } 221 | 222 | type freezeOperatorMsgHandler struct{ accts sdk.AccountMapper } 223 | 224 | // Freeze operator's message logic. 225 | // Admins can freeze their own entity's operator accounts. 226 | func (h freezeOperatorMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 227 | // ensure proper message 228 | cm, ok := msg.(FreezeOperatorMsg) 229 | if !ok { 230 | return ErrWrongMsgFormat("expected FreezeOperatorMsg").Result() 231 | } 232 | // ensure admin exists 233 | admin, err := getActiveAdmin(ctx, h.accts, cm.Admin) 234 | if err != nil { 235 | return err.Result() 236 | } 237 | // ensure operator exists 238 | operator, err := getActiveOperator(ctx, h.accts, cm.Target) 239 | if err != nil { 240 | return err.Result() 241 | } 242 | if !BelongToSameEntity(admin, operator) { 243 | return ErrWrongSigner("admin and operator do not belong to the same entity").Result() 244 | } 245 | // Construct a new account 246 | operator.Active = false 247 | h.accts.SetAccount(ctx, operator) 248 | return sdk.Result{} 249 | } 250 | 251 | // FreezeAdminMsgHandler returns the handler's method. 252 | func FreezeAdminMsgHandler(accts sdk.AccountMapper) sdk.Handler { 253 | return freezeAdminMsgHandler{accts}.Do 254 | } 255 | 256 | type freezeAdminMsgHandler struct{ accts sdk.AccountMapper } 257 | 258 | // Freeze admin's message logic. 259 | // Clearing house Admins can freeze any other admin. 260 | func (h freezeAdminMsgHandler) Do(ctx sdk.Context, msg sdk.Msg) sdk.Result { 261 | // ensure proper message 262 | cm, ok := msg.(FreezeAdminMsg) 263 | if !ok { 264 | return ErrWrongMsgFormat("expected FreezeAdminMsg").Result() 265 | } 266 | // ensure clearing house admin exists 267 | if _, err := getCHActiveAdmin(ctx, h.accts, cm.Admin); err != nil { 268 | return err.Result() 269 | } 270 | // ensure target admin exists 271 | admin, err := getActiveAdmin(ctx, h.accts, cm.Target) 272 | if err != nil { 273 | return err.Result() 274 | } 275 | // Construct a new account 276 | admin.Active = false 277 | h.accts.SetAccount(ctx, admin) 278 | return sdk.Result{} 279 | } 280 | 281 | // Business logic 282 | 283 | func validateAdminAndCreateOperator(ctx sdk.Context, accts sdk.AccountMapper, 284 | creatorAddr crypto.Address, pub crypto.PubKey) (*AppAccount, sdk.Error) { 285 | creator, err := getActiveAdmin(ctx, accts, creatorAddr) 286 | if err != nil { 287 | return nil, err 288 | } 289 | // ensure new account does not exist 290 | if accts.GetAccount(ctx, pub.Address()) != nil { 291 | return nil, ErrInvalidAccount("couldn't create the account, it already exists") 292 | } 293 | return NewOpUser(pub, creator.GetAddress(), creator.LegalEntityName(), creator.LegalEntityType()), nil 294 | } 295 | 296 | func validateCHAdminAndCreateXEntityAdmin(ctx sdk.Context, accts sdk.AccountMapper, 297 | creatorAddr crypto.Address, pub crypto.PubKey, ent LegalEntity) (*AppAccount, sdk.Error) { 298 | if _, err := getCHActiveAdmin(ctx, accts, creatorAddr); err != nil { 299 | return nil, err 300 | } 301 | // ensure new account does not exist 302 | if accts.GetAccount(ctx, pub.Address()) != nil { 303 | return nil, ErrInvalidAccount("couldn't create the account, it already exists") 304 | } 305 | return NewAdminUser(pub, creatorAddr, ent.LegalEntityName(), ent.LegalEntityType()), nil 306 | } 307 | 308 | // Transfers money from the sender to the recipient 309 | func moveMoney(accts sdk.AccountMapper, ctx sdk.Context, sender *AppAccount, recipient *AppAccount, 310 | amount sdk.Coin, senderMustBePositive bool, recipientMustBePositive bool) sdk.Error { 311 | transfer := sdk.Coins{amount} 312 | // first verify funds 313 | sender.Coins = sender.Coins.Minus(transfer) 314 | if senderMustBePositive && !sender.Coins.IsNotNegative() { 315 | return ErrInvalidAmount("sender has insufficient funds") 316 | } 317 | // transfer may be negative 318 | recipient.Coins = recipient.Coins.Plus(transfer) 319 | if recipientMustBePositive && !recipient.Coins.IsNotNegative() { 320 | return ErrInvalidAmount("recipient has insufficient funds") 321 | } 322 | // now make the transfer and save the result 323 | accts.SetAccount(ctx, sender) 324 | accts.SetAccount(ctx, recipient) 325 | return nil 326 | } 327 | 328 | // Auxiliary functions 329 | 330 | func getCHActiveAdmin(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address) (*AppAccount, sdk.Error) { 331 | return getUserAccountWithGetterAndEntityType(ctx, accts, addr, getActiveAdmin, IsClearingHouse) 332 | } 333 | 334 | func getCHActiveOperator(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address) (*AppAccount, sdk.Error) { 335 | return getUserAccountWithGetterAndEntityType(ctx, accts, addr, getActiveOperator, IsClearingHouse) 336 | } 337 | 338 | func getUserAccountWithGetterAndEntityType(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address, 339 | accGetter func(sdk.Context, sdk.AccountMapper, crypto.Address) (*AppAccount, sdk.Error), 340 | entityTypeCheck func(LegalEntity) bool) (*AppAccount, sdk.Error) { 341 | account, err := accGetter(ctx, accts, addr) 342 | if err != nil { 343 | return nil, err 344 | } 345 | if !entityTypeCheck(account) { 346 | return nil, ErrWrongSigner(account.LegalEntityType()) 347 | } 348 | return account, nil 349 | } 350 | 351 | func getActiveAssetWithEntityType(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address, 352 | entityTypeCheck func(LegalEntity) bool) (*AppAccount, sdk.Error) { 353 | account, err := getActiveAsset(ctx, accts, addr) 354 | if err != nil { 355 | return nil, err 356 | } 357 | if !entityTypeCheck(account) { 358 | return nil, ErrWrongSigner(account.LegalEntityType()) 359 | } 360 | return account, nil 361 | } 362 | 363 | func getActiveAdmin(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address) (*AppAccount, sdk.Error) { 364 | return getUser(ctx, accts, addr, true, true) 365 | } 366 | 367 | func getActiveOperator(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address) (*AppAccount, sdk.Error) { 368 | return getUser(ctx, accts, addr, true, false) 369 | } 370 | 371 | func getUser(ctx sdk.Context, accts sdk.AccountMapper, 372 | addr crypto.Address, wantActive, wantAdmin bool) (*AppAccount, sdk.Error) { 373 | rawAccount := accts.GetAccount(ctx, addr) 374 | if rawAccount == nil { 375 | return nil, ErrInvalidAccount("account does not exist") 376 | } 377 | account := rawAccount.(*AppAccount) 378 | if !account.IsUser() { 379 | return nil, ErrWrongSigner("invalid account type") 380 | } 381 | if wantActive && !account.Active { 382 | return nil, ErrInactiveUser(fmt.Sprintf("%v", addr)) 383 | } 384 | if !wantActive && account.Active { 385 | return nil, ErrInactiveUser(fmt.Sprintf("%v", addr)) 386 | } 387 | if wantAdmin && !account.IsAdmin() { 388 | return nil, ErrWrongSigner("must be admin") 389 | } 390 | if !wantAdmin && account.IsAdmin() { 391 | return nil, ErrWrongSigner("must not be admin") 392 | } 393 | return account, nil 394 | } 395 | 396 | func getActiveAsset(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address) (*AppAccount, sdk.Error) { 397 | return getAsset(ctx, accts, addr, true) 398 | } 399 | 400 | func getAsset(ctx sdk.Context, accts sdk.AccountMapper, addr crypto.Address, wantActive bool) (*AppAccount, sdk.Error) { 401 | rawAccount := accts.GetAccount(ctx, addr) 402 | if rawAccount == nil { 403 | return nil, ErrInvalidAccount("account does not exist") 404 | } 405 | account := rawAccount.(*AppAccount) 406 | if !account.IsAsset() { 407 | return nil, ErrWrongSigner("invalid account type") 408 | } 409 | if wantActive && !account.Active { 410 | return nil, ErrInactiveUser(fmt.Sprintf("%v", addr)) 411 | } 412 | if !wantActive && account.Active { 413 | return nil, ErrInactiveUser(fmt.Sprintf("%v", addr)) 414 | } 415 | return account, nil 416 | } 417 | -------------------------------------------------------------------------------- /types/msgs_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/stretchr/testify/assert" 9 | crypto "github.com/tendermint/go-crypto" 10 | ) 11 | 12 | func TestDepositMsg_ValidateBasic(t *testing.T) { 13 | coin := sdk.Coin{Amount: 100, Denom: "ATM"} 14 | coinNegative := sdk.Coin{Amount: -100, Denom: "ATM"} 15 | short := crypto.Address("foo") 16 | long := crypto.Address("hefkuhwqekufghwqekufgwqekufgkwuqgfkugfkuwgek") 17 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 18 | addr2 := crypto.GenPrivKeyEd25519().PubKey().Address() 19 | addr3 := crypto.GenPrivKeyEd25519().PubKey().Address() 20 | 21 | type fields struct { 22 | Operator crypto.Address 23 | Sender crypto.Address 24 | Recipient crypto.Address 25 | Amount sdk.Coin 26 | } 27 | tests := []struct { 28 | name string 29 | fields fields 30 | errorCode sdk.CodeType 31 | }{ 32 | { 33 | "empty msg", 34 | fields{}, 35 | CodeInvalidAmount, 36 | }, 37 | { 38 | "no denom", 39 | fields{Amount: sdk.Coin{Amount: 100}}, 40 | CodeInvalidAmount, 41 | }, 42 | { 43 | "no amount", 44 | fields{Amount: sdk.Coin{Denom: "Foo"}}, 45 | CodeInvalidAmount, 46 | }, 47 | { 48 | "missing operator address", 49 | fields{Amount: coin}, 50 | CodeInvalidAddress, 51 | }, 52 | { 53 | "missing sender address", 54 | fields{Amount: coin, Operator: addr}, 55 | CodeInvalidAddress, 56 | }, 57 | { 58 | "short address", 59 | fields{Amount: coin, Operator: short}, 60 | CodeInvalidAddress, 61 | }, 62 | { 63 | "long sender address", 64 | fields{Amount: coin, Operator: addr, Sender: long}, 65 | CodeInvalidAddress, 66 | }, 67 | { 68 | "long recipient address", 69 | fields{Amount: coin, Operator: addr, Sender: addr, Recipient: long}, 70 | CodeInvalidAddress, 71 | }, 72 | { 73 | "same address", 74 | fields{Amount: coin, Operator: addr2, Sender: addr, Recipient: addr}, 75 | CodeInvalidAddress, 76 | }, 77 | { 78 | "proper addresses", 79 | fields{Amount: coin, Operator: addr, Sender: addr2, Recipient: addr3}, 80 | sdk.CodeOK, 81 | }, 82 | { 83 | "negative amount", 84 | fields{Amount: coinNegative, Operator: addr, Sender: addr2, Recipient: addr3}, 85 | CodeInvalidAmount, 86 | }, 87 | } 88 | for _, tt := range tests { 89 | t.Run(tt.name, func(t *testing.T) { 90 | d := DepositMsg{ 91 | Operator: tt.fields.Operator, 92 | Sender: tt.fields.Sender, 93 | Recipient: tt.fields.Recipient, 94 | Amount: tt.fields.Amount, 95 | } 96 | got := d.ValidateBasic() 97 | if got == nil { 98 | assert.True(t, tt.errorCode.IsOK()) 99 | } else { 100 | assert.Equal(t, tt.errorCode, got.ABCICode()) 101 | } 102 | }) 103 | } 104 | } 105 | func TestSettleMsg_ValidateBasic(t *testing.T) { 106 | coin := sdk.Coin{Amount: 100, Denom: "ATM"} 107 | coinNegative := sdk.Coin{Amount: -100, Denom: "ATM"} 108 | short := crypto.Address("foo") 109 | long := crypto.Address("hefkuhwqekufghwqekufgwqekufgkwuqgfkugfkuwgek") 110 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 111 | addr2 := crypto.GenPrivKeyEd25519().PubKey().Address() 112 | addr3 := crypto.GenPrivKeyEd25519().PubKey().Address() 113 | 114 | type fields struct { 115 | Operator crypto.Address 116 | Sender crypto.Address 117 | Recipient crypto.Address 118 | Amount sdk.Coin 119 | } 120 | tests := []struct { 121 | name string 122 | fields fields 123 | errorCode sdk.CodeType 124 | }{ 125 | { 126 | "empty msg", 127 | fields{}, 128 | CodeInvalidAmount, 129 | }, 130 | { 131 | "no denom", 132 | fields{Amount: sdk.Coin{Amount: 100}}, 133 | CodeInvalidAmount, 134 | }, 135 | { 136 | "no amount", 137 | fields{Amount: sdk.Coin{Denom: "Foo"}}, 138 | CodeInvalidAmount, 139 | }, 140 | { 141 | "missing operator address", 142 | fields{Amount: coin}, 143 | CodeInvalidAddress, 144 | }, 145 | { 146 | "short address", 147 | fields{Amount: coin, Operator: short, Sender: short, Recipient: short}, 148 | CodeInvalidAddress, 149 | }, 150 | { 151 | "long address", 152 | fields{Amount: coin, Operator: long, Sender: short, Recipient: long}, 153 | CodeInvalidAddress, 154 | }, 155 | { 156 | "long address2", 157 | fields{Amount: coin, Operator: addr, Sender: addr2, Recipient: long}, 158 | CodeInvalidAddress, 159 | }, 160 | { 161 | "sender and recipient got same address", 162 | fields{Amount: coin, Operator: addr, Sender: addr2, Recipient: addr2}, 163 | CodeInvalidAddress, 164 | }, 165 | { 166 | "proper address", 167 | fields{Amount: coin, Operator: addr, Sender: addr2, Recipient: addr3}, 168 | sdk.CodeOK, 169 | }, 170 | { 171 | "proper negative amount", 172 | fields{Amount: coinNegative, Operator: addr3, Sender: addr, Recipient: addr2}, 173 | sdk.CodeOK, 174 | }, 175 | } 176 | for _, tt := range tests { 177 | t.Run(tt.name, func(t *testing.T) { 178 | d := SettleMsg{ 179 | Operator: tt.fields.Operator, 180 | Sender: tt.fields.Sender, 181 | Recipient: tt.fields.Recipient, 182 | Amount: tt.fields.Amount, 183 | } 184 | got := d.ValidateBasic() 185 | if got == nil { 186 | assert.True(t, tt.errorCode.IsOK()) 187 | } else { 188 | assert.Equal(t, tt.errorCode, got.ABCICode()) 189 | } 190 | }) 191 | } 192 | } 193 | func TestWithdrawMsg_ValidateBasic(t *testing.T) { 194 | coin := sdk.Coin{Amount: 100, Denom: "ATM"} 195 | coinNegative := sdk.Coin{Amount: -100, Denom: "ATM"} 196 | short := crypto.Address("foo") 197 | long := crypto.Address("hefkuhwqekufghwqekufgwqekufgkwuqgfkugfkuwgek") 198 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 199 | addr2 := crypto.GenPrivKeyEd25519().PubKey().Address() 200 | addr3 := crypto.GenPrivKeyEd25519().PubKey().Address() 201 | 202 | type fields struct { 203 | Sender crypto.Address 204 | Recipient crypto.Address 205 | Operator crypto.Address 206 | Amount sdk.Coin 207 | } 208 | tests := []struct { 209 | name string 210 | fields fields 211 | errorCode sdk.CodeType 212 | }{ 213 | {"empty msg", fields{}, CodeInvalidAmount}, 214 | {"no denom", fields{Amount: sdk.Coin{Amount: 100}}, CodeInvalidAmount}, 215 | {"no amount", fields{Amount: sdk.Coin{Denom: "Foo"}}, CodeInvalidAmount}, 216 | {"missing address", fields{Amount: coin}, CodeInvalidAddress}, 217 | {"short address", fields{Amount: coin, Sender: short, Recipient: short}, CodeInvalidAddress}, 218 | {"long address", fields{Amount: coin, Sender: long, Recipient: long}, CodeInvalidAddress}, 219 | {"long address2", fields{Amount: coin, Sender: addr, Recipient: long}, CodeInvalidAddress}, 220 | {"same address", fields{Amount: coin, Sender: addr, Recipient: addr, Operator: addr3}, CodeInvalidAddress}, 221 | {"missing proper address", fields{Amount: coin, Sender: addr, Recipient: addr2}, CodeInvalidAddress}, 222 | {"negative amount", fields{Amount: coinNegative, Sender: addr, Recipient: addr2}, CodeInvalidAmount}, 223 | {"proper address", fields{Amount: coin, Sender: addr, Recipient: addr2, Operator: addr3}, sdk.CodeOK}, 224 | } 225 | for _, tt := range tests { 226 | t.Run(tt.name, func(t *testing.T) { 227 | w := WithdrawMsg{ 228 | Operator: tt.fields.Operator, 229 | Sender: tt.fields.Sender, 230 | Recipient: tt.fields.Recipient, 231 | Amount: tt.fields.Amount, 232 | } 233 | got := w.ValidateBasic() 234 | if got == nil { 235 | assert.True(t, tt.errorCode.IsOK()) 236 | } else { 237 | assert.Equal(t, tt.errorCode, got.ABCICode(), got.Error()) 238 | } 239 | }) 240 | } 241 | } 242 | 243 | func TestCreateAssetAccountMsg_ValidateBasic(t *testing.T) { 244 | creatorAddress := crypto.GenPrivKeyEd25519().PubKey().Address() 245 | newPubKey := crypto.GenPrivKeyEd25519().PubKey() 246 | type fields struct { 247 | Creator crypto.Address 248 | PubKey crypto.PubKey 249 | } 250 | tests := []struct { 251 | name string 252 | fields fields 253 | want sdk.CodeType 254 | }{ 255 | {"new CH acc ok", fields{creatorAddress, newPubKey}, sdk.CodeOK}, 256 | {"new CUS acc ok", fields{creatorAddress, newPubKey}, sdk.CodeOK}, 257 | {"new GCM acc ok", fields{creatorAddress, newPubKey}, sdk.CodeOK}, 258 | {"new ICM acc ok", fields{creatorAddress, newPubKey}, sdk.CodeOK}, 259 | {"creator is nil", fields{nil, newPubKey}, CodeInvalidAddress}, 260 | {"invalid creator len", fields{crypto.Address("short"), newPubKey}, CodeInvalidAddress}, 261 | {"new pubkey is empty", fields{creatorAddress, crypto.PubKey{}}, CodeInvalidPubKey}, 262 | {"same creator and acct", fields{newPubKey.Address(), newPubKey}, CodeInvalidPubKey}, 263 | } 264 | for _, tt := range tests { 265 | t.Run(tt.name, func(t *testing.T) { 266 | msg := CreateAssetAccountMsg{ 267 | Creator: tt.fields.Creator, 268 | PubKey: tt.fields.PubKey, 269 | } 270 | got := msg.ValidateBasic() 271 | if got == nil { 272 | assert.True(t, tt.want == sdk.CodeOK) 273 | } else { 274 | assert.Equal(t, tt.want, got.ABCICode(), got.Error()) 275 | } 276 | }) 277 | } 278 | } 279 | 280 | func TestBaseCreateUserMsg_ValidateBasic(t *testing.T) { 281 | pub := crypto.GenPrivKeyEd25519().PubKey() 282 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 283 | long := crypto.Address("hefkuhwqekufghwqekufgwqekufgkwuqgfkugfkuwgek") 284 | type fields struct { 285 | Creator crypto.Address 286 | PubKey crypto.PubKey 287 | } 288 | tests := []struct { 289 | name string 290 | fields fields 291 | want sdk.CodeType 292 | }{ 293 | {"nil pubkey", fields{Creator: pub.Address()}, CodeInvalidPubKey}, 294 | {"nil address", fields{PubKey: pub}, CodeInvalidAddress}, 295 | {"empty address", fields{Creator: crypto.Address(""), PubKey: pub}, CodeInvalidAddress}, 296 | {"short address", fields{Creator: crypto.Address("foo"), PubKey: pub}, CodeInvalidAddress}, 297 | {"long address", fields{Creator: long, PubKey: pub}, CodeInvalidAddress}, 298 | {"self create", fields{Creator: pub.Address(), PubKey: pub}, CodeSelfCreate}, 299 | {"good to go", fields{Creator: addr, PubKey: pub}, sdk.CodeOK}, 300 | } 301 | for _, tt := range tests { 302 | t.Run(tt.name, func(t *testing.T) { 303 | msg := BaseCreateUserMsg{ 304 | Creator: tt.fields.Creator, 305 | PubKey: tt.fields.PubKey, 306 | } 307 | got := msg.ValidateBasic() 308 | if got != nil { 309 | assert.Equal(t, tt.want, got.ABCICode(), got.Error()) 310 | } else { 311 | assert.Equal(t, tt.want, sdk.CodeOK) 312 | } 313 | }) 314 | } 315 | } 316 | 317 | func TestCreateAdminMsg_ValidateBasic(t *testing.T) { 318 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 319 | pub := crypto.GenPrivKeyEd25519().PubKey() 320 | validEntity := BaseLegalEntity{ 321 | EntityName: "CH", 322 | EntityType: EntityClearingHouse, 323 | } 324 | validCreateUser := BaseCreateUserMsg{ 325 | Creator: addr, 326 | PubKey: pub, 327 | } 328 | type fields struct { 329 | cm BaseCreateUserMsg 330 | le BaseLegalEntity 331 | } 332 | tests := []struct { 333 | name string 334 | fields fields 335 | want sdk.CodeType 336 | }{ 337 | {"nil pubkey", fields{cm: BaseCreateUserMsg{nil, crypto.PubKey{}}, le: validEntity}, CodeInvalidPubKey}, 338 | {"invalid entity type", fields{cm: validCreateUser, le: BaseLegalEntity{EntityName: "CH", EntityType: "invalid"}}, CodeInvalidEntity}, 339 | {"empty entity name", fields{cm: validCreateUser, le: BaseLegalEntity{EntityName: " ", EntityType: EntityClearingHouse}}, CodeInvalidEntity}, 340 | {"self create", fields{cm: BaseCreateUserMsg{Creator: pub.Address(), PubKey: pub}, le: validEntity}, CodeSelfCreate}, 341 | {"ok", fields{cm: validCreateUser, le: validEntity}, sdk.CodeOK}, 342 | } 343 | for _, tt := range tests { 344 | t.Run(tt.name, func(t *testing.T) { 345 | msg := CreateAdminMsg{ 346 | BaseCreateUserMsg: tt.fields.cm, 347 | BaseLegalEntity: tt.fields.le, 348 | } 349 | got := msg.ValidateBasic() 350 | if got != nil { 351 | assert.Equal(t, tt.want, got.ABCICode(), got.ABCILog) 352 | } else { 353 | assert.Equal(t, tt.want, sdk.CodeOK) 354 | } 355 | }) 356 | } 357 | } 358 | 359 | func TestBaseFreezeAccountMsg_ValidateBasic(t *testing.T) { 360 | addr1 := crypto.GenPrivKeyEd25519().PubKey().Address() 361 | addr2 := crypto.GenPrivKeyEd25519().PubKey().Address() 362 | type fields struct { 363 | a crypto.Address 364 | t crypto.Address 365 | } 366 | tests := []struct { 367 | name string 368 | fields fields 369 | want sdk.CodeType 370 | }{ 371 | {"empty msg", fields{}, CodeInvalidAddress}, 372 | {"empty target", fields{a: addr1}, CodeInvalidAddress}, 373 | {"self freeze", fields{a: addr1, t: addr1}, CodeSelfFreeze}, 374 | {"ok", fields{a: addr1, t: addr2}, sdk.CodeOK}, 375 | } 376 | for _, tt := range tests { 377 | t.Run(tt.name, func(t *testing.T) { 378 | msg := BaseFreezeAccountMsg{ 379 | Admin: tt.fields.a, 380 | Target: tt.fields.t, 381 | } 382 | got := msg.ValidateBasic() 383 | if got != nil { 384 | assert.Equal(t, tt.want, got.ABCICode(), got.ABCILog) 385 | } else { 386 | assert.Equal(t, tt.want, sdk.CodeOK) 387 | } 388 | }) 389 | } 390 | } 391 | 392 | func TestDepositMsg_GetSigners(t *testing.T) { 393 | msg := DepositMsg{ 394 | Operator: crypto.GenPrivKeyEd25519().PubKey().Address(), 395 | } 396 | got := msg.GetSigners() 397 | assert.Equal(t, len(got), 1) 398 | assert.True(t, bytes.Equal(msg.Operator, got[0])) 399 | } 400 | 401 | func TestSettleMsg_GetSigners(t *testing.T) { 402 | msg := SettleMsg{ 403 | Operator: crypto.GenPrivKeyEd25519().PubKey().Address(), 404 | } 405 | got := msg.GetSigners() 406 | assert.Equal(t, len(got), 1) 407 | assert.True(t, bytes.Equal(msg.Operator, got[0])) 408 | } 409 | 410 | func TestWithdrawMsg_GetSigners(t *testing.T) { 411 | msg := WithdrawMsg{ 412 | Operator: crypto.GenPrivKeyEd25519().PubKey().Address(), 413 | } 414 | got := msg.GetSigners() 415 | assert.Equal(t, len(got), 1) 416 | assert.True(t, bytes.Equal(msg.Operator, got[0])) 417 | } 418 | func TestBaseCreateUserMsg_GetSigners(t *testing.T) { 419 | msg := BaseCreateUserMsg{ 420 | Creator: crypto.GenPrivKeyEd25519().PubKey().Address(), 421 | } 422 | got := msg.GetSigners() 423 | assert.Equal(t, len(got), 1) 424 | assert.True(t, bytes.Equal(msg.Creator, got[0])) 425 | } 426 | func TestCreateAssetAccountMsg_GetSigners(t *testing.T) { 427 | msg := CreateAssetAccountMsg{ 428 | Creator: crypto.GenPrivKeyEd25519().PubKey().Address(), 429 | } 430 | got := msg.GetSigners() 431 | assert.Equal(t, len(got), 1) 432 | assert.True(t, bytes.Equal(msg.Creator, got[0])) 433 | } 434 | 435 | func TestBaseFreezeAccountMsg_GetSigners(t *testing.T) { 436 | msg := BaseFreezeAccountMsg{ 437 | Admin: crypto.GenPrivKeyEd25519().PubKey().Address(), 438 | Target: crypto.GenPrivKeyEd25519().PubKey().Address(), 439 | } 440 | got := msg.GetSigners() 441 | assert.Equal(t, len(got), 1) 442 | assert.True(t, bytes.Equal(msg.Admin, got[0])) 443 | } 444 | 445 | func TestMessageTypes(t *testing.T) { 446 | deposit := DepositMsg{} 447 | settle := SettleMsg{} 448 | withdraw := WithdrawMsg{} 449 | createOp := CreateOperatorMsg{} 450 | createAd := CreateAdminMsg{} 451 | createAsset := CreateAssetAccountMsg{} 452 | freezeOp := FreezeOperatorMsg{} 453 | freezeAd := FreezeAdminMsg{} 454 | assert.Equal(t, deposit.Type(), DepositType) 455 | assert.Equal(t, settle.Type(), SettlementType) 456 | assert.Equal(t, withdraw.Type(), WithdrawType) 457 | assert.Equal(t, createOp.Type(), CreateOperatorType) 458 | assert.Equal(t, createAd.Type(), CreateAdminType) 459 | assert.Equal(t, createAsset.Type(), CreateAssetAccountType) 460 | assert.Equal(t, freezeOp.Type(), FreezeOperatorType) 461 | assert.Equal(t, freezeAd.Type(), FreezeAdminType) 462 | } 463 | 464 | func Test_NewCreateAdminMsg(t *testing.T) { 465 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 466 | pub := crypto.GenPrivKeyEd25519().PubKey() 467 | type args struct { 468 | creator sdk.Address 469 | pubkey crypto.PubKey 470 | entityName string 471 | entityType string 472 | } 473 | tests := []struct { 474 | name string 475 | args args 476 | wantMsg CreateAdminMsg 477 | }{ 478 | {"nil", args{nil, crypto.PubKey{}, "", ""}, CreateAdminMsg{}}, 479 | {"CreateAdminMsg", args{addr, pub, "entityName", "typ"}, CreateAdminMsg{ 480 | BaseCreateUserMsg{PubKey: pub, Creator: addr}, 481 | BaseLegalEntity{EntityName: "entityName", EntityType: "typ"}}}, 482 | } 483 | for _, tt := range tests { 484 | t.Run(tt.name, func(t *testing.T) { 485 | got := NewCreateAdminMsg(tt.args.creator, tt.args.pubkey, tt.args.entityName, tt.args.entityType) 486 | assert.Equal(t, got, tt.wantMsg) 487 | }) 488 | } 489 | } 490 | 491 | func Test_NewCreateOperatorMsg(t *testing.T) { 492 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 493 | pub := crypto.GenPrivKeyEd25519().PubKey() 494 | type args struct { 495 | creator sdk.Address 496 | pubkey crypto.PubKey 497 | } 498 | tests := []struct { 499 | name string 500 | args args 501 | wantMsg CreateOperatorMsg 502 | }{ 503 | {"nil", args{nil, crypto.PubKey{}}, CreateOperatorMsg{}}, 504 | {"CreateAdminMsg", args{addr, pub}, CreateOperatorMsg{ 505 | BaseCreateUserMsg{PubKey: pub, Creator: addr}, 506 | }}, 507 | } 508 | for _, tt := range tests { 509 | t.Run(tt.name, func(t *testing.T) { 510 | got := NewCreateOperatorMsg(tt.args.creator, tt.args.pubkey) 511 | assert.Equal(t, got, tt.wantMsg) 512 | }) 513 | } 514 | } 515 | 516 | func Test_NewCreateAssetAccountMsg(t *testing.T) { 517 | addr := crypto.GenPrivKeyEd25519().PubKey().Address() 518 | pub := crypto.GenPrivKeyEd25519().PubKey() 519 | type args struct { 520 | creator sdk.Address 521 | pubkey crypto.PubKey 522 | } 523 | tests := []struct { 524 | name string 525 | args args 526 | wantMsg CreateAssetAccountMsg 527 | }{ 528 | {"nil", args{nil, crypto.PubKey{}}, CreateAssetAccountMsg{}}, 529 | {"CreateAdminMsg", args{addr, pub}, CreateAssetAccountMsg{PubKey: pub, Creator: addr}}, 530 | } 531 | for _, tt := range tests { 532 | t.Run(tt.name, func(t *testing.T) { 533 | got := NewCreateAssetAccountMsg(tt.args.creator, tt.args.pubkey) 534 | assert.Equal(t, got, tt.wantMsg) 535 | }) 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /types/handler_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmos/cosmos-sdk/baseapp" 7 | "github.com/cosmos/cosmos-sdk/store" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/stretchr/testify/assert" 10 | abci "github.com/tendermint/abci/types" 11 | crypto "github.com/tendermint/go-crypto" 12 | dbm "github.com/tendermint/tmlibs/db" 13 | ) 14 | 15 | // TestRegisterRoutes is an end-to-end test, making sure a normal workflow is 16 | // supported and passing all messages through the router to simulate production code path 17 | func TestRegisterRoutes(t *testing.T) { 18 | accts, ctx := fakeAccountMapper() 19 | 20 | _, chOpPriv := fakeUser(accts, ctx, EntityClearingHouse) 21 | op := chOpPriv.PubKey().Address() 22 | _, cust := fakeAsset(accts, ctx, nil, EntityCustodian) 23 | _, member := fakeAsset(accts, ctx, nil, EntityIndividualClearingMember) 24 | _, ch := fakeAsset(accts, ctx, nil, EntityClearingHouse) 25 | _, member2 := fakeAsset(accts, ctx, nil, EntityGeneralClearingMember) 26 | 27 | router := baseapp.NewRouter() 28 | RegisterRoutes(router, accts) 29 | 30 | type args struct { 31 | ctx sdk.Context 32 | msg sdk.Msg 33 | } 34 | tests := []struct { 35 | name string 36 | args args 37 | expect sdk.CodeType 38 | custBal sdk.Coins 39 | memberBal sdk.Coins 40 | member2Bal sdk.Coins 41 | chBal sdk.Coins 42 | }{ 43 | { 44 | "good deposit", args{ctx: ctx, msg: DepositMsg{Operator: op, Sender: cust, Recipient: member, Amount: sdk.Coin{"USD", 5000}}}, 45 | sdk.CodeOK, sdk.Coins{{"USD", -5000}}, sdk.Coins{{"USD", 5000}}, sdk.Coins{}, sdk.Coins{}, 46 | }, 47 | { 48 | "deposit2", 49 | args{ctx: ctx, msg: DepositMsg{Operator: op, Sender: cust, Recipient: member2, Amount: sdk.Coin{"USD", 7777}}}, 50 | sdk.CodeOK, 51 | sdk.Coins{{"USD", -12777}}, 52 | sdk.Coins{{"USD", 5000}}, 53 | sdk.Coins{{"USD", 7777}}, 54 | sdk.Coins{}, 55 | }, 56 | { 57 | "settlement", 58 | args{ctx: ctx, msg: SettleMsg{Operator: op, Sender: ch, Recipient: member, Amount: sdk.Coin{"USD", 3000}}}, 59 | sdk.CodeOK, 60 | sdk.Coins{{"USD", -12777}}, 61 | sdk.Coins{{"USD", 8000}}, 62 | sdk.Coins{{"USD", 7777}}, 63 | sdk.Coins{{"USD", -3000}}, 64 | }, 65 | { 66 | "counter settlement", 67 | args{ctx: ctx, msg: SettleMsg{Operator: op, Sender: ch, Recipient: member2, Amount: sdk.Coin{"USD", -3000}}}, 68 | sdk.CodeOK, 69 | sdk.Coins{{"USD", -12777}}, 70 | sdk.Coins{{"USD", 8000}}, 71 | sdk.Coins{{"USD", 4777}}, 72 | sdk.Coins{}, 73 | }, 74 | { 75 | "withdraw", 76 | args{ctx: ctx, msg: WithdrawMsg{Operator: op, Sender: member, Recipient: cust, Amount: sdk.Coin{"USD", 5500}}}, 77 | sdk.CodeOK, 78 | sdk.Coins{{"USD", -7277}}, 79 | sdk.Coins{{"USD", 2500}}, 80 | sdk.Coins{{"USD", 4777}}, 81 | sdk.Coins{}, 82 | }, 83 | } 84 | 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | h := router.Route(tt.args.msg.Type()) 88 | got := h(tt.args.ctx, tt.args.msg) 89 | assert.Equal(t, tt.expect, got.Code, got.Log) 90 | 91 | c := accts.GetAccount(ctx, cust) 92 | assert.Equal(t, tt.custBal, c.GetCoins()) 93 | 94 | m := accts.GetAccount(ctx, member) 95 | assert.Equal(t, tt.memberBal, m.GetCoins()) 96 | 97 | m2 := accts.GetAccount(ctx, member2) 98 | assert.Equal(t, tt.member2Bal, m2.GetCoins()) 99 | 100 | chAsset := accts.GetAccount(ctx, ch) 101 | assert.Equal(t, tt.chBal, chAsset.GetCoins()) 102 | }) 103 | } 104 | } 105 | func Test_depositMsgHandler_Do(t *testing.T) { 106 | accts, ctx := fakeAccountMapper() 107 | cCoins := sdk.Coins{{"EUR", 5000}, {"USD", 1000}} 108 | mCoins := sdk.Coins{} 109 | 110 | _, chAdm := fakeAdmin(accts, ctx, EntityClearingHouse) 111 | chAdmAddr := chAdm.PubKey().Address() 112 | _, chOpPriv := fakeUser(accts, ctx, EntityClearingHouse) 113 | _, cust := fakeAsset(accts, ctx, cCoins, EntityCustodian) 114 | _, member := fakeAsset(accts, ctx, mCoins, EntityIndividualClearingMember) 115 | chOp := chOpPriv.PubKey().Address() 116 | inactiveOp, _ := fakeInactiveUser(accts, ctx, EntityClearingHouse) 117 | _, inactiveAssetAddr := fakeInactiveAssetWithEntityName(accts, ctx, cCoins, EntityGeneralClearingMember, EntityGeneralClearingMember) 118 | 119 | type args struct { 120 | ctx sdk.Context 121 | msg sdk.Msg 122 | } 123 | tests := []struct { 124 | name string 125 | args args 126 | expect sdk.CodeType 127 | cBal sdk.Coins 128 | mBal sdk.Coins 129 | }{ 130 | { 131 | "admins ain't allowed", args{ctx: ctx, msg: DepositMsg{Operator: chAdmAddr, Sender: cust, 132 | Recipient: member, Amount: sdk.Coin{"USD", 700}}}, CodeWrongSigner, cCoins, sdk.Coins{}, 133 | }, 134 | { 135 | "inactive operator", args{ctx: ctx, msg: DepositMsg{Operator: inactiveOp.Address, Sender: cust, 136 | Recipient: member, Amount: sdk.Coin{"USD", 700}}}, CodeInactiveAccount, cCoins, sdk.Coins{}, 137 | }, 138 | { 139 | "no returns", args{ctx: ctx, msg: DepositMsg{Operator: chOp, Sender: member, 140 | Recipient: cust, Amount: sdk.Coin{"USD", 200}}}, CodeWrongSigner, cCoins, sdk.Coins{}, // sdk.Coins{} 141 | }, 142 | { 143 | "good deposit", args{ctx: ctx, msg: DepositMsg{Operator: chOp, Sender: cust, Recipient: member, 144 | Amount: sdk.Coin{"USD", 700}}}, sdk.CodeOK, sdk.Coins{{"EUR", 5000}, {"USD", 300}}, sdk.Coins{{"USD", 700}}, 145 | }, 146 | { 147 | // allow the custodian to go negative 148 | "overdraft", args{ctx: ctx, msg: DepositMsg{Operator: chOp, Sender: cust, Recipient: member, Amount: sdk.Coin{"EUR", 10000}}}, 149 | sdk.CodeOK, sdk.Coins{{"EUR", -5000}, {"USD", 300}}, sdk.Coins{{"EUR", 10000}, {"USD", 700}}, 150 | }, 151 | { 152 | // want an active asset for the recipient 153 | "overdraft", args{ctx: ctx, msg: DepositMsg{Operator: chOp, Sender: cust, Recipient: inactiveAssetAddr, Amount: sdk.Coin{"EUR", 10000}}}, 154 | CodeInactiveAccount, sdk.Coins{{"EUR", -5000}, {"USD", 300}}, sdk.Coins{{"EUR", 10000}, {"USD", 700}}, 155 | }, 156 | } 157 | for _, tt := range tests { 158 | t.Run(tt.name, func(t *testing.T) { 159 | handler := DepositMsgHandler(accts) 160 | got := handler(tt.args.ctx, tt.args.msg) 161 | assert.Equal(t, tt.expect, got.Code, got.Log) 162 | 163 | c := accts.GetAccount(ctx, cust) 164 | assert.Equal(t, tt.cBal, c.GetCoins()) 165 | 166 | m := accts.GetAccount(ctx, member) 167 | assert.Equal(t, tt.mBal, m.GetCoins()) 168 | }) 169 | } 170 | } 171 | 172 | func Test_settleMsgHandler_Do(t *testing.T) { 173 | accts, ctx := fakeAccountMapper() 174 | clhCoins := sdk.Coins{{"EUR", 5000}, {"USD", 1000}} 175 | mCoins := sdk.Coins{{"USD", 1000}} 176 | 177 | chAdmAcc, _ := fakeAdmin(accts, ctx, EntityClearingHouse) 178 | chOpAcc, _ := fakeUser(accts, ctx, EntityClearingHouse) 179 | chAdm := chAdmAcc.Address 180 | chOp := chOpAcc.Address 181 | _, clh := fakeAssetWithEntityName(accts, ctx, clhCoins, chOpAcc.LegalEntityType(), EntityClearingHouse) 182 | _, member := fakeAssetWithEntityName(accts, ctx, mCoins, "ICM", EntityIndividualClearingMember) 183 | 184 | type args struct { 185 | ctx sdk.Context 186 | msg sdk.Msg 187 | } 188 | tests := []struct { 189 | name string 190 | args args 191 | expect sdk.CodeType 192 | cBal sdk.Coins 193 | mBal sdk.Coins 194 | }{ 195 | { 196 | "admins cannot settle", 197 | args{ctx: ctx, msg: SettleMsg{Operator: chAdm, Sender: member, Recipient: clh, Amount: sdk.Coin{"USD", 200}}}, 198 | CodeWrongSigner, clhCoins, mCoins, 199 | }, 200 | { 201 | "no returns", 202 | args{ctx: ctx, msg: SettleMsg{Operator: chOp, Sender: member, Recipient: clh, Amount: sdk.Coin{"USD", 200}}}, 203 | CodeWrongSigner, clhCoins, mCoins, 204 | }, 205 | { 206 | "negative good settle", 207 | args{ctx: ctx, msg: SettleMsg{Operator: chOp, Sender: clh, Recipient: member, Amount: sdk.Coin{"USD", -500}}}, 208 | sdk.CodeOK, sdk.Coins{{"EUR", 5000}, {"USD", 1500}}, sdk.Coins{{"USD", 500}}, 209 | }, 210 | 211 | { 212 | "positive good settle", 213 | args{ctx: ctx, msg: SettleMsg{Operator: chOp, Sender: clh, Recipient: member, Amount: sdk.Coin{"USD", 500}}}, 214 | sdk.CodeOK, sdk.Coins{{"EUR", 5000}, {"USD", 1000}}, sdk.Coins{{"USD", 1000}}, 215 | }, 216 | { 217 | // allow the clearing house to go negative 218 | "overdraft", 219 | args{ctx: ctx, msg: SettleMsg{Operator: chOp, Sender: clh, Recipient: member, Amount: sdk.Coin{"EUR", 10000}}}, 220 | sdk.CodeOK, sdk.Coins{{"EUR", -5000}, {"USD", 1000}}, sdk.Coins{{"EUR", 10000}, {"USD", 1000}}, 221 | }, 222 | } 223 | for _, tt := range tests { 224 | t.Run(tt.name, func(t *testing.T) { 225 | handler := SettleMsgHandler(accts) 226 | got := handler(tt.args.ctx, tt.args.msg) 227 | assert.Equal(t, tt.expect, got.Code, got.Log) 228 | 229 | c := accts.GetAccount(ctx, clh) 230 | assert.Equal(t, tt.cBal, c.GetCoins()) 231 | 232 | m := accts.GetAccount(ctx, member) 233 | assert.Equal(t, tt.mBal, m.GetCoins()) 234 | }) 235 | } 236 | } 237 | 238 | func Test_withdrawMsgHandler_Do(t *testing.T) { 239 | accts, ctx := fakeAccountMapper() 240 | mCoins := sdk.Coins{{"EUR", 5000}, {"USD", 1000}} 241 | custCoins := sdk.Coins{} 242 | 243 | _, cust := fakeAsset(accts, ctx, custCoins, EntityCustodian) 244 | _, member := fakeAsset(accts, ctx, mCoins, EntityIndividualClearingMember) 245 | chAdm, _ := fakeAdmin(accts, ctx, EntityClearingHouse) 246 | opAcc, _ := fakeUser(accts, ctx, EntityClearingHouse) 247 | admPub := chAdm.Address 248 | operator := opAcc.Address 249 | tests := []struct { 250 | name string 251 | msg sdk.Msg 252 | expect sdk.CodeType 253 | cBal sdk.Coins 254 | mBal sdk.Coins 255 | }{ 256 | { 257 | "no returns", WithdrawMsg{Sender: cust, Recipient: member, Operator: operator, Amount: sdk.Coin{"USD", 200}}, 258 | CodeWrongSigner, sdk.Coins{}, mCoins, 259 | }, 260 | { 261 | "good Withdraw", WithdrawMsg{Sender: member, Recipient: cust, Operator: operator, Amount: sdk.Coin{"USD", 500}}, 262 | sdk.CodeOK, sdk.Coins{{"USD", 500}}, sdk.Coins{{"EUR", 5000}, {"USD", 500}}, 263 | }, 264 | { 265 | "admins can't withdraw", WithdrawMsg{Sender: member, Recipient: cust, Operator: admPub, Amount: sdk.Coin{"USD", 500}}, 266 | CodeWrongSigner, sdk.Coins{{"USD", 500}}, sdk.Coins{{"EUR", 5000}, {"USD", 500}}, 267 | }, 268 | { 269 | "invalid address", WithdrawMsg{Sender: member, Recipient: cust, Operator: crypto.GenPrivKeyEd25519().PubKey().Address(), Amount: sdk.Coin{"USD", 500}}, 270 | CodeInvalidAccount, sdk.Coins{{"USD", 500}}, sdk.Coins{{"EUR", 5000}, {"USD", 500}}, 271 | }, 272 | } 273 | for _, tt := range tests { 274 | t.Run(tt.name, func(t *testing.T) { 275 | handler := WithdrawMsgHandler(accts) 276 | got := handler(ctx, tt.msg) 277 | assert.Equal(t, tt.expect, got.Code, got.Log) 278 | 279 | c := accts.GetAccount(ctx, cust) 280 | assert.Equal(t, tt.cBal, c.GetCoins()) 281 | 282 | m := accts.GetAccount(ctx, member) 283 | assert.Equal(t, tt.mBal, m.GetCoins()) 284 | }) 285 | } 286 | } 287 | 288 | func Test_createAssetAccountMsgHandler_Do(t *testing.T) { 289 | accts, ctx := fakeAccountMapper() 290 | createAccount := func(typ, entityName string, isAdm bool) crypto.Address { 291 | if isAdm { 292 | ac, _ := fakeAdminWithEntityName(accts, ctx, entityName, typ) 293 | return ac.PubKey.Address() 294 | } 295 | ac, _ := fakeUserWithEntityName(accts, ctx, entityName, typ) 296 | return ac.PubKey.Address() 297 | } 298 | mkKey := func() crypto.PubKey { 299 | return crypto.GenPrivKeyEd25519().PubKey() 300 | } 301 | chOp := createAccount(EntityClearingHouse, "CH", false) 302 | chAdm := createAccount(EntityClearingHouse, "CH", true) 303 | custOp := createAccount(EntityCustodian, "CUST", false) 304 | custAdm := createAccount(EntityCustodian, "CUST", true) 305 | existing, _ := fakeAsset(accts, ctx, nil, "CUST") 306 | tests := []struct { 307 | name string 308 | msg sdk.Msg 309 | expect sdk.CodeType 310 | }{ 311 | {"CH admin can create CH asset", CreateAssetAccountMsg{Creator: chAdm, PubKey: mkKey()}, sdk.CodeOK}, 312 | {"CH op cannot create CH asset", CreateAssetAccountMsg{Creator: chOp, PubKey: mkKey()}, CodeWrongSigner}, 313 | {"CUST admin can create CUST asset", CreateAssetAccountMsg{Creator: custAdm, PubKey: mkKey()}, sdk.CodeOK}, 314 | {"CUST op cannot create CUST asset", CreateAssetAccountMsg{Creator: custOp, PubKey: mkKey()}, CodeWrongSigner}, 315 | {"account already exists", CreateAssetAccountMsg{Creator: custAdm, PubKey: existing.PubKey}, CodeInvalidAccount}, 316 | } 317 | for _, tt := range tests { 318 | t.Run(tt.name, func(t *testing.T) { 319 | handler := CreateAssetAccountMsgHandler(accts) 320 | got := handler(ctx, tt.msg) 321 | assert.Equal(t, tt.expect, got.Code, got.Log) 322 | 323 | concreteMsg := tt.msg.(CreateAssetAccountMsg) 324 | newAcc := accts.GetAccount(ctx, concreteMsg.PubKey.Address()) 325 | if tt.name == "account already exists" { 326 | assert.True(t, newAcc != nil) 327 | } else if tt.expect == sdk.CodeOK { 328 | creator := accts.GetAccount(ctx, concreteMsg.Creator).(*AppAccount) 329 | assert.True(t, newAcc != nil) 330 | concAcc := newAcc.(*AppAccount) 331 | assert.Equal(t, concAcc.BaseLegalEntity, creator.BaseLegalEntity) 332 | } else { 333 | assert.True(t, newAcc == nil) 334 | } 335 | 336 | }) 337 | } 338 | } 339 | 340 | func Test_validateAdminAndCreateOperator(t *testing.T) { 341 | accts, ctx := fakeAccountMapper() 342 | newAccPub := crypto.GenPrivKeyEd25519().PubKey() 343 | inactiveAdm, _ := fakeInactiveAdmin(accts, ctx, EntityClearingHouse) 344 | admin, _ := fakeAdmin(accts, ctx, EntityClearingHouse) 345 | opCreated := NewOpUser(newAccPub, admin.Address, admin.LegalEntityName(), admin.LegalEntityType()) 346 | operator, _ := fakeUser(accts, ctx, EntityClearingHouse) 347 | type args struct { 348 | creatorAddr crypto.Address 349 | pub crypto.PubKey 350 | } 351 | tests := []struct { 352 | name string 353 | args args 354 | want *AppAccount 355 | want1 sdk.CodeType 356 | }{ 357 | {"inactive admin cannot create", args{inactiveAdm.Address, newAccPub}, nil, CodeInactiveAccount}, 358 | {"operator cannot create operator", args{operator.Address, newAccPub}, nil, CodeWrongSigner}, 359 | {"admin can create operator", args{admin.Address, newAccPub}, opCreated, sdk.CodeOK}, 360 | {"account already exists", args{admin.Address, operator.PubKey}, nil, CodeInvalidAccount}, 361 | } 362 | for _, tt := range tests { 363 | t.Run(tt.name, func(t *testing.T) { 364 | got, got1 := validateAdminAndCreateOperator(ctx, accts, tt.args.creatorAddr, tt.args.pub) 365 | if got == nil || tt.want == nil { 366 | assert.True(t, got == tt.want) 367 | } else { 368 | assert.True(t, accountEqual(tt.want, got)) 369 | } 370 | if got1 == nil { 371 | assert.Equal(t, tt.want1, sdk.CodeOK) 372 | } else { 373 | assert.Equal(t, tt.want1, got1.ABCICode(), got1.ABCILog()) 374 | } 375 | }) 376 | } 377 | } 378 | 379 | func Test_validateCHAdminAndCreateXEntityAdmin(t *testing.T) { 380 | accts, ctx := fakeAccountMapper() 381 | ent := BaseLegalEntity{EntityName: "ICM", EntityType: EntityIndividualClearingMember} 382 | newAccPub := crypto.GenPrivKeyEd25519().PubKey() 383 | admin, _ := fakeAdmin(accts, ctx, EntityClearingHouse) 384 | inactiveAdmin, _ := fakeInactiveAdmin(accts, ctx, EntityClearingHouse) 385 | adminCreated := NewAdminUser(newAccPub, admin.Address, ent.LegalEntityName(), ent.LegalEntityType()) 386 | icmAdmin, _ := fakeAdmin(accts, ctx, EntityIndividualClearingMember) 387 | chOperator, _ := fakeUser(accts, ctx, EntityClearingHouse) 388 | custOperator, _ := fakeUser(accts, ctx, EntityCustodian) 389 | type args struct { 390 | creatorAddr crypto.Address 391 | pub crypto.PubKey 392 | ent LegalEntity 393 | } 394 | tests := []struct { 395 | name string 396 | args args 397 | want *AppAccount 398 | want1 sdk.CodeType 399 | }{ 400 | {"foreign operator cannot create", args{custOperator.Address, newAccPub, ent}, nil, CodeWrongSigner}, 401 | {"foreign admin cannot create", args{icmAdmin.Address, newAccPub, ent}, nil, CodeWrongSigner}, 402 | {"operator cannot create admin", args{chOperator.Address, newAccPub, ent}, nil, CodeWrongSigner}, 403 | {"admin can create admin", args{admin.Address, newAccPub, ent}, adminCreated, sdk.CodeOK}, 404 | {"existing account", args{admin.Address, icmAdmin.PubKey, icmAdmin.BaseLegalEntity}, nil, CodeInvalidAccount}, 405 | {"inactive admin cannot create", args{inactiveAdmin.Address, newAccPub, ent}, nil, CodeInactiveAccount}, 406 | } 407 | for _, tt := range tests { 408 | t.Run(tt.name, func(t *testing.T) { 409 | got, got1 := validateCHAdminAndCreateXEntityAdmin(ctx, accts, tt.args.creatorAddr, tt.args.pub, tt.args.ent) 410 | if got == nil || tt.want == nil { 411 | assert.True(t, got == tt.want) 412 | } else { 413 | assert.True(t, accountEqual(tt.want, got)) 414 | } 415 | if got1 == nil { 416 | assert.Equal(t, tt.want1, sdk.CodeOK) 417 | } else { 418 | assert.Equal(t, tt.want1, got1.ABCICode(), got1.ABCILog()) 419 | } 420 | }) 421 | } 422 | } 423 | 424 | //---------------- helpers -------------------- 425 | 426 | func fakeAccountMapper() (sdk.AccountMapper, sdk.Context) { 427 | db := dbm.NewMemDB() 428 | ms := store.NewCommitMultiStore(db) 429 | 430 | key := sdk.NewKVStoreKey("test") 431 | ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) 432 | err := ms.LoadLatestVersion() 433 | if err != nil { 434 | panic(err) 435 | } 436 | 437 | accts := NewAccountMapper(key) 438 | h := abci.Header{ 439 | Height: 100, 440 | ChainID: "clear-chain", 441 | } 442 | ctx := sdk.NewContext(ms, h, false, []byte{1, 2, 3, 4}) // DeliverTx 443 | 444 | return accts, ctx 445 | } 446 | 447 | func fakeUser(accts sdk.AccountMapper, ctx sdk.Context, typ string) (*AppAccount, crypto.PrivKey) { 448 | return fakeUserWithEntityName(accts, ctx, typ, typ) 449 | } 450 | 451 | func fakeInactiveUser(accts sdk.AccountMapper, ctx sdk.Context, typ string) (*AppAccount, crypto.PrivKey) { 452 | acct, priv := makeUser(typ, typ) 453 | acct.Active = false 454 | accts.SetAccount(ctx, acct) 455 | return acct, priv 456 | } 457 | 458 | func fakeUserWithEntityName(accts sdk.AccountMapper, ctx sdk.Context, entname, typ string) (*AppAccount, crypto.PrivKey) { 459 | acct, priv := makeUser(entname, typ) 460 | accts.SetAccount(ctx, acct) 461 | return acct, priv 462 | } 463 | 464 | func fakeAdmin(accts sdk.AccountMapper, ctx sdk.Context, typ string) (*AppAccount, crypto.PrivKey) { 465 | return fakeAdminWithEntityName(accts, ctx, typ, typ) 466 | } 467 | 468 | func fakeInactiveAdmin(accts sdk.AccountMapper, ctx sdk.Context, typ string) (*AppAccount, crypto.PrivKey) { 469 | acct, priv := makeAdminUser(typ, typ) 470 | acct.Active = false 471 | accts.SetAccount(ctx, acct) 472 | return acct, priv 473 | } 474 | 475 | func fakeAdminWithEntityName(accts sdk.AccountMapper, ctx sdk.Context, entname, typ string) (*AppAccount, crypto.PrivKey) { 476 | acct, priv := makeAdminUser(entname, typ) 477 | accts.SetAccount(ctx, acct) 478 | return acct, priv 479 | } 480 | 481 | func fakeAsset(accts sdk.AccountMapper, ctx sdk.Context, cash sdk.Coins, typ string) (*AppAccount, crypto.Address) { 482 | return fakeAssetWithEntityName(accts, ctx, cash, typ, typ) 483 | } 484 | 485 | func fakeInactiveAssetWithEntityName(accts sdk.AccountMapper, ctx sdk.Context, 486 | cash sdk.Coins, entname, typ string) (*AppAccount, crypto.Address) { 487 | acct, addr := makeAssetAccount(cash, entname, typ) 488 | acct.Active = false 489 | accts.SetAccount(ctx, acct) 490 | return acct, addr 491 | } 492 | 493 | func fakeAssetWithEntityName(accts sdk.AccountMapper, ctx sdk.Context, cash sdk.Coins, entname, typ string) (*AppAccount, crypto.Address) { 494 | acct, addr := makeAssetAccount(cash, entname, typ) 495 | accts.SetAccount(ctx, acct) 496 | return acct, addr 497 | } 498 | 499 | func Test_createOperatorMsgHandler_Do(t *testing.T) { 500 | accts, ctx := fakeAccountMapper() 501 | newPub := crypto.GenPrivKeyEd25519().PubKey() 502 | admin, _ := fakeAdminWithEntityName(accts, ctx, "member", EntityIndividualClearingMember) 503 | operator, _ := fakeUser(accts, ctx, EntityCustodian) 504 | asset, _ := fakeAsset(accts, ctx, nil, EntityGeneralClearingMember) 505 | admAddr := admin.Address 506 | opAddr := operator.Address 507 | assetAddr := asset.Address 508 | tests := []struct { 509 | name string 510 | msg BaseCreateUserMsg 511 | want sdk.CodeType 512 | }{ 513 | {"admin can create", BaseCreateUserMsg{Creator: admAddr, PubKey: newPub}, sdk.CodeOK}, 514 | {"already exists", BaseCreateUserMsg{Creator: admAddr, PubKey: admin.PubKey}, CodeInvalidAccount}, 515 | {"operator cannot create", BaseCreateUserMsg{Creator: opAddr, PubKey: newPub}, CodeWrongSigner}, 516 | {"asset cannot create", BaseCreateUserMsg{Creator: assetAddr, PubKey: newPub}, CodeWrongSigner}, 517 | } 518 | for _, tt := range tests { 519 | t.Run(tt.name, func(t *testing.T) { 520 | msg := CreateOperatorMsg{tt.msg} 521 | h := createOperatorMsgHandler{ 522 | accts: accts, 523 | } 524 | assert.Equal(t, tt.want, h.Do(ctx, msg).Code) 525 | }) 526 | } 527 | } 528 | 529 | func Test_createAdminMsgHandler_Do(t *testing.T) { 530 | accts, ctx := fakeAccountMapper() 531 | newPub := crypto.GenPrivKeyEd25519().PubKey() 532 | chAdmin, _ := fakeAdminWithEntityName(accts, ctx, "clearing house", EntityClearingHouse) 533 | nonchAdmin, _ := fakeAdminWithEntityName(accts, ctx, "member", EntityIndividualClearingMember) 534 | operator, _ := fakeUser(accts, ctx, EntityCustodian) 535 | asset, _ := fakeAsset(accts, ctx, nil, EntityGeneralClearingMember) 536 | chAdmAddr := chAdmin.Address 537 | nonchAdmAddr := nonchAdmin.Address 538 | opAddr := operator.Address 539 | assetAddr := asset.Address 540 | tests := []struct { 541 | name string 542 | msg CreateAdminMsg 543 | want sdk.CodeType 544 | }{ 545 | {"non CH admin cannot create", CreateAdminMsg{BaseCreateUserMsg{ 546 | Creator: nonchAdmAddr, PubKey: newPub}, BaseLegalEntity{}}, CodeWrongSigner}, 547 | {"CH admin can create", CreateAdminMsg{BaseCreateUserMsg{ 548 | Creator: chAdmAddr, PubKey: newPub}, BaseLegalEntity{}}, sdk.CodeOK}, 549 | {"operator cannot create", CreateAdminMsg{BaseCreateUserMsg{ 550 | Creator: opAddr, PubKey: newPub}, BaseLegalEntity{}}, CodeWrongSigner}, 551 | {"asset cannot create", CreateAdminMsg{BaseCreateUserMsg{ 552 | Creator: assetAddr, PubKey: newPub}, BaseLegalEntity{}}, CodeWrongSigner}, 553 | } 554 | for _, tt := range tests { 555 | t.Run(tt.name, func(t *testing.T) { 556 | h := createAdminMsgHandler{ 557 | accts: accts, 558 | } 559 | got := h.Do(ctx, tt.msg) 560 | assert.Equal(t, tt.want, got.Code, got.Log) 561 | }) 562 | } 563 | } 564 | 565 | func Test_freezeOperatorMsgHandler_Do(t *testing.T) { 566 | accts, ctx := fakeAccountMapper() 567 | admin, _ := fakeAdminWithEntityName(accts, ctx, "qweasdzxc", EntityIndividualClearingMember) 568 | admAddr := admin.Address 569 | inactiveAdmin, _ := fakeAdminWithEntityName(accts, ctx, "qweasdzxc", EntityIndividualClearingMember) 570 | inactiveAdmin.Active = false 571 | accts.SetAccount(ctx, inactiveAdmin) 572 | inadmAddr := inactiveAdmin.Address 573 | operator, _ := fakeUserWithEntityName(accts, ctx, admin.LegalEntityName(), EntityIndividualClearingMember) 574 | opAddr := operator.Address 575 | inactiveOperator, _ := fakeUserWithEntityName(accts, ctx, admin.LegalEntityName(), EntityIndividualClearingMember) 576 | inactiveOperator.Active = false 577 | accts.SetAccount(ctx, inactiveOperator) 578 | inopAddr := inactiveOperator.Address 579 | foreignOperator, _ := fakeUser(accts, ctx, EntityIndividualClearingMember) 580 | fopAddr := foreignOperator.Address 581 | asset, _ := fakeAsset(accts, ctx, nil, EntityGeneralClearingMember) 582 | assetAddr := asset.Address 583 | tests := []struct { 584 | name string 585 | msg BaseFreezeAccountMsg 586 | want sdk.CodeType 587 | }{ 588 | {"inactive admin", BaseFreezeAccountMsg{Admin: inadmAddr, Target: opAddr}, CodeInactiveAccount}, 589 | {"inactive op", BaseFreezeAccountMsg{Admin: admAddr, Target: inopAddr}, CodeInactiveAccount}, 590 | {"op can't freeze", BaseFreezeAccountMsg{Admin: opAddr}, CodeWrongSigner}, 591 | {"foreign operator", BaseFreezeAccountMsg{Admin: admAddr, Target: fopAddr}, CodeWrongSigner}, 592 | {"invalid account", BaseFreezeAccountMsg{Admin: admAddr, Target: assetAddr}, CodeWrongSigner}, 593 | {"ok", BaseFreezeAccountMsg{Admin: admAddr, Target: opAddr}, sdk.CodeOK}, 594 | } 595 | for _, tt := range tests { 596 | t.Run(tt.name, func(t *testing.T) { 597 | h := freezeOperatorMsgHandler{accts: accts} 598 | got := h.Do(ctx, FreezeOperatorMsg{tt.msg}) 599 | assert.Equal(t, tt.want, got.Code, got.Log) 600 | if got.Code == sdk.CodeOK { 601 | acct := accts.GetAccount(ctx, tt.msg.Target) 602 | ca := acct.(*AppAccount) 603 | assert.False(t, ca.IsActive()) 604 | } 605 | }) 606 | } 607 | } 608 | 609 | func Test_freezeAdminMsgHandler_Do(t *testing.T) { 610 | accts, ctx := fakeAccountMapper() 611 | chAdmin, _ := fakeAdmin(accts, ctx, EntityClearingHouse) 612 | chAdmAddr := chAdmin.Address 613 | inactiveChAdmin, _ := fakeAdminWithEntityName(accts, ctx, chAdmin.EntityName, EntityClearingHouse) 614 | inactiveChAdmin.Active = false 615 | accts.SetAccount(ctx, inactiveChAdmin) 616 | inChAdmAddr := inactiveChAdmin.Address 617 | operator, _ := fakeUser(accts, ctx, EntityIndividualClearingMember) 618 | opAddr := operator.Address 619 | foreignAdmin, _ := fakeAdmin(accts, ctx, EntityIndividualClearingMember) 620 | foaAddr := foreignAdmin.Address 621 | foreignInactiveAdmin, _ := fakeAdmin(accts, ctx, EntityIndividualClearingMember) 622 | foreignInactiveAdmin.Active = false 623 | accts.SetAccount(ctx, foreignInactiveAdmin) 624 | foaInAddr := foreignInactiveAdmin.Address 625 | asset, _ := fakeAsset(accts, ctx, nil, EntityGeneralClearingMember) 626 | assetAddr := asset.Address 627 | tests := []struct { 628 | name string 629 | msg BaseFreezeAccountMsg 630 | want sdk.CodeType 631 | }{ 632 | {"can't freeze op", BaseFreezeAccountMsg{Admin: inChAdmAddr, Target: foaAddr}, CodeInactiveAccount}, 633 | {"inactive admin", BaseFreezeAccountMsg{Admin: inChAdmAddr, Target: foaAddr}, CodeInactiveAccount}, 634 | {"inactive target", BaseFreezeAccountMsg{Admin: chAdmAddr, Target: foaInAddr}, CodeInactiveAccount}, 635 | {"op can't freeze", BaseFreezeAccountMsg{Admin: opAddr}, CodeWrongSigner}, 636 | {"invalid account", BaseFreezeAccountMsg{Admin: chAdmAddr, Target: assetAddr}, CodeWrongSigner}, 637 | {"can't freeze op", BaseFreezeAccountMsg{Admin: chAdmAddr, Target: opAddr}, CodeWrongSigner}, 638 | {"ok", BaseFreezeAccountMsg{Admin: chAdmAddr, Target: foaAddr}, sdk.CodeOK}, 639 | } 640 | for _, tt := range tests { 641 | t.Run(tt.name, func(t *testing.T) { 642 | h := freezeAdminMsgHandler{accts: accts} 643 | got := h.Do(ctx, FreezeAdminMsg{tt.msg}) 644 | assert.Equal(t, tt.want, got.Code, got.Log) 645 | if got.Code == sdk.CodeOK { 646 | acct := accts.GetAccount(ctx, tt.msg.Target) 647 | ca := acct.(*AppAccount) 648 | assert.False(t, ca.IsActive()) 649 | } 650 | }) 651 | } 652 | } 653 | --------------------------------------------------------------------------------