diff --git a/types/account.go b/types/account.go new file mode 100644 index 0000000000..28f8d7b08f --- /dev/null +++ b/types/account.go @@ -0,0 +1,21 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} + +type Account interface { + Get(key interface{}) (value interface{}) + Address() []byte + PubKey() crypto.PubKey + + // Serialize the Account to bytes. + Bytes() []byte +} + +type AccountStore interface { + GetAccount(addr []byte) Account + SetAccount(acc Account) +} diff --git a/types/coins.go b/types/coins.go new file mode 100644 index 0000000000..e34f7d1bdb --- /dev/null +++ b/types/coins.go @@ -0,0 +1,197 @@ +package types + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +func (coin Coin) String() string { + return fmt.Sprintf("%v%v", coin.Amount, coin.Denom) +} + +//regex codes for extracting coins from string +var reDenom = regexp.MustCompile("([^\\d\\W]+)") +var reAmt = regexp.MustCompile("(\\d+)") + +func ParseCoin(str string) (Coin, error) { + + var coin Coin + + if len(str) > 0 { + amt, err := strconv.Atoi(reAmt.FindString(str)) + if err != nil { + return coin, err + } + denom := reDenom.FindString(str) + coin = Coin{denom, int64(amt)} + } + + return coin, nil +} + +//---------------------------------------- + +type Coins []Coin + +func (coins Coins) String() string { + if len(coins) == 0 { + return "" + } + + out := "" + for _, coin := range coins { + out += fmt.Sprintf("%v,", coin.String()) + } + return out[:len(out)-1] +} + +func ParseCoins(str string) (Coins, error) { + + split := strings.Split(str, ",") + var coins []Coin + + for _, el := range split { + if len(el) > 0 { + coin, err := ParseCoin(el) + if err != nil { + return coins, err + } + coins = append(coins, coin) + } + } + + return coins, nil +} + +// Must be sorted, and not have 0 amounts +func (coins Coins) IsValid() bool { + switch len(coins) { + case 0: + return true + case 1: + return coins[0].Amount != 0 + default: + lowDenom := coins[0].Denom + for _, coin := range coins[1:] { + if coin.Denom <= lowDenom { + return false + } + if coin.Amount == 0 { + return false + } + // we compare each coin against the last denom + lowDenom = coin.Denom + } + return true + } +} + +// TODO: handle empty coins! +// Currently appends an empty coin ... +func (coinsA Coins) Plus(coinsB Coins) Coins { + sum := []Coin{} + indexA, indexB := 0, 0 + lenA, lenB := len(coinsA), len(coinsB) + for { + if indexA == lenA { + if indexB == lenB { + return sum + } else { + return append(sum, coinsB[indexB:]...) + } + } else if indexB == lenB { + return append(sum, coinsA[indexA:]...) + } + coinA, coinB := coinsA[indexA], coinsB[indexB] + switch strings.Compare(coinA.Denom, coinB.Denom) { + case -1: + sum = append(sum, coinA) + indexA += 1 + case 0: + if coinA.Amount+coinB.Amount == 0 { + // ignore 0 sum coin type + } else { + sum = append(sum, Coin{ + Denom: coinA.Denom, + Amount: coinA.Amount + coinB.Amount, + }) + } + indexA += 1 + indexB += 1 + case 1: + sum = append(sum, coinB) + indexB += 1 + } + } + return sum +} + +func (coins Coins) Negative() Coins { + res := make([]Coin, 0, len(coins)) + for _, coin := range coins { + res = append(res, Coin{ + Denom: coin.Denom, + Amount: -coin.Amount, + }) + } + return res +} + +func (coinsA Coins) Minus(coinsB Coins) Coins { + return coinsA.Plus(coinsB.Negative()) +} + +func (coinsA Coins) IsGTE(coinsB Coins) bool { + diff := coinsA.Minus(coinsB) + if len(diff) == 0 { + return true + } + return diff.IsNonnegative() +} + +func (coins Coins) IsZero() bool { + return len(coins) == 0 +} + +func (coinsA Coins) IsEqual(coinsB Coins) bool { + if len(coinsA) != len(coinsB) { + return false + } + for i := 0; i < len(coinsA); i++ { + if coinsA[i] != coinsB[i] { + return false + } + } + return true +} + +func (coins Coins) IsPositive() bool { + if len(coins) == 0 { + return false + } + for _, coinAmount := range coins { + if coinAmount.Amount <= 0 { + return false + } + } + return true +} + +func (coins Coins) IsNonnegative() bool { + if len(coins) == 0 { + return true + } + for _, coinAmount := range coins { + if coinAmount.Amount < 0 { + return false + } + } + return true +} diff --git a/types/coins_test.go b/types/coins_test.go new file mode 100644 index 0000000000..8cbc708a49 --- /dev/null +++ b/types/coins_test.go @@ -0,0 +1,85 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCoins(t *testing.T) { + assert := assert.New(t) + + //Define the coins to be used in tests + good := Coins{ + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + Coin{"TREE", 1}, + } + neg := good.Negative() + sum := good.Plus(neg) + empty := Coins{ + Coin{"GOLD", 0}, + } + badSort1 := Coins{ + Coin{"TREE", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order + Coin{"GAS", 1}, + Coin{"TREE", 1}, + Coin{"MINERAL", 1}, + } + badAmt := Coins{ + Coin{"GAS", 1}, + Coin{"TREE", 0}, + Coin{"MINERAL", 1}, + } + dup := Coins{ + Coin{"GAS", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + + assert.True(good.IsValid(), "Coins are valid") + assert.True(good.IsPositive(), "Expected coins to be positive: %v", good) + assert.True(good.IsGTE(empty), "Expected %v to be >= %v", good, empty) + assert.False(neg.IsPositive(), "Expected neg coins to not be positive: %v", neg) + assert.Zero(len(sum), "Expected 0 coins") + assert.False(badSort1.IsValid(), "Coins are not sorted") + assert.False(badSort2.IsValid(), "Coins are not sorted") + assert.False(badAmt.IsValid(), "Coins cannot include 0 amounts") + assert.False(dup.IsValid(), "Duplicate coin") + +} + +//Test the parse coin and parse coins functionality +func TestParse(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + makeCoin := func(str string) Coin { + coin, err := ParseCoin(str) + require.Nil(err) + return coin + } + + makeCoins := func(str string) Coins { + coin, err := ParseCoins(str) + require.Nil(err) + return coin + } + + //testing ParseCoin Function + assert.Equal(Coin{}, makeCoin(""), "ParseCoin makes bad empty coin") + assert.Equal(Coin{"fooCoin", 1}, makeCoin("1fooCoin"), "ParseCoin makes bad coins") + assert.Equal(Coin{"barCoin", 10}, makeCoin("10 barCoin"), "ParseCoin makes bad coins") + + //testing ParseCoins Function + assert.True(Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")), + "ParseCoins doesn't parse a single coin") + assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")), + "ParseCoins doesn't properly parse two coins") + assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")), + "ParseCoins doesn't properly parse two coins which use spaces") +} diff --git a/types/msg.go b/types/msg.go new file mode 100644 index 0000000000..e7738bd73d --- /dev/null +++ b/types/msg.go @@ -0,0 +1,27 @@ +package types + +import "github.com/tendermint/tmlibs/crypto" + +// The parsed tx bytes is called a Msg. +type Msg interface { + Get(key interface{}) (value interface{}) + Origin() (tx []byte) + + // Signers() returns the crypto.PubKey of signers + // responsible for signing the Msg. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns pubkeys in some deterministic order + // CONTRACT: Get(MsgKeySigners) compatible. + Signers() []crypto.PubKey + + // Signatures() returns the crypto.Signature of sigenrs + // who signed the Msg. + // CONTRACT: Length returned is same as length of + // pubkeys returned from MsgKeySigners, and the order + // matches. + // CONTRACT: If the signature is missing (ie the Msg is + // invalid), then the corresponding signature is + // .Empty(). + // CONTRACT: Get(MsgKeySignatures) compatible. + Signatures() []crypto.Signature +} diff --git a/x/auth/signature.go b/x/auth/decorator.go similarity index 100% rename from x/auth/signature.go rename to x/auth/decorator.go