-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
426 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Package chain contains core types and their helper functions to deal with the | ||
// blockchain. | ||
package chain | ||
|
||
import ( | ||
iruntime "internal/runtime" | ||
) | ||
|
||
// Address represents a bech32-encoded blockchain address. | ||
type Address string | ||
|
||
func (a Address) IsValid() bool { | ||
// TODO: make stdlib crypto/bech32 | ||
_, _, ok := DecodeBech32(string(a)) | ||
return ok | ||
} | ||
|
||
func NewAddressFromImportPath(importPath string) { | ||
// TODO: find better name | ||
panic("not implemented") | ||
} | ||
|
||
// A Realm represents an on-chain entity which has an address. This is generally | ||
// an Externally Owned Account (ie. a "user") or a code realm, which also has an | ||
// associated import path. | ||
type Realm struct { | ||
address Address | ||
importPath string | ||
} | ||
|
||
func (r Realm) Address() Address { | ||
return r.addr | ||
} | ||
|
||
func (r Realm) ImportPath() string { | ||
return r.importPath | ||
} | ||
|
||
func (r Realm) IsUser() bool { | ||
return r.importPath == "" | ||
} | ||
|
||
// NewCodeRealm creates a new realm, representing a code realm with a published | ||
// import path, existing on-chain. | ||
func NewCodeRealm(importPath string) Realm { | ||
if !iruntime.IsTesting() && !iruntime.IsRuntime() { | ||
panic("realms may only be created in testing or by the runtime package") | ||
} | ||
return Realm{ | ||
address: NewAddressFromImportPath(importPath), | ||
importPath: importPath, | ||
} | ||
} | ||
|
||
// NewUserRealm creates a new realm, representing a regular user. | ||
func NewUserRealm(address Address) Realm { | ||
if !iruntime.IsTesting() && !iruntime.IsRuntime() { | ||
panic("realms may only be created in testing or by the runtime package") | ||
} | ||
return Realm{ | ||
address: address, | ||
} | ||
} | ||
|
||
// Banker is an interface to access the blockchain's native banker. | ||
type Banker interface { | ||
// Balance returns the balance in the bank for the given address, as a list | ||
// of coins. | ||
Balance(addr Address) (dst Coins) | ||
|
||
// Supply returns the circulating supply of the given denomination. | ||
Supply(denom string) int64 | ||
|
||
// Send sends the given Coins between the from and to addresses. | ||
Send(from, to Address, amt Coins) | ||
|
||
// Issue issues the coin with the given denomination | ||
Issue(addr Address, denom string, amount int64) | ||
Burn(addr Address, denom string, amount int64) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package chain | ||
|
||
// Coin holds some amount of one currency. | ||
// A negative amount is invalid. | ||
type Coin struct { | ||
Denom string `json:"denom"` | ||
Amount int64 `json:"amount"` | ||
} | ||
|
||
// NewCoin returns a new coin with a denomination and amount | ||
func NewCoin(denom string, amount int64) Coin { | ||
return Coin{ | ||
Denom: denom, | ||
Amount: amount, | ||
} | ||
} | ||
|
||
// String provides a human-readable representation of a coin | ||
func (c Coin) String() string { | ||
return strconv.Itoa(int(c.Amount)) + c.Denom | ||
} | ||
|
||
// IsGTE returns true if they are the same type and the receiver is | ||
// an equal or greater value | ||
func (c Coin) IsGTE(other Coin) bool { | ||
mustMatchDenominations(c.Denom, other.Denom) | ||
|
||
return c.Amount >= other.Amount | ||
} | ||
|
||
// IsLT returns true if they are the same type and the receiver is | ||
// a smaller value | ||
func (c Coin) IsLT(other Coin) bool { | ||
mustMatchDenominations(c.Denom, other.Denom) | ||
|
||
return c.Amount < other.Amount | ||
} | ||
|
||
// IsEqual returns true if the two sets of Coins have the same value | ||
func (c Coin) IsEqual(other Coin) bool { | ||
mustMatchDenominations(c.Denom, other.Denom) | ||
|
||
return c.Amount == other.Amount | ||
} | ||
|
||
// Add adds amounts of two coins with same denom. | ||
// If the coins differ in denom then it panics. | ||
// An overflow or underflow panics. | ||
// An invalid result panics. | ||
func (c Coin) Add(other Coin) Coin { | ||
mustMatchDenominations(c.Denom, other.Denom) | ||
|
||
sum, ok := overflow.Add64(c.Amount, other.Amount) | ||
if !ok { | ||
panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) | ||
} | ||
|
||
c.Amount = sum | ||
return c | ||
} | ||
|
||
// Sub subtracts amounts of two coins with same denom. | ||
// If the coins differ in denom then it panics. | ||
// An overflow or underflow panics. | ||
// An invalid result panics. | ||
func (c Coin) Sub(other Coin) Coin { | ||
mustMatchDenominations(c.Denom, other.Denom) | ||
|
||
dff, ok := overflow.Sub64(c.Amount, other.Amount) | ||
if !ok { | ||
panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) | ||
} | ||
c.Amount = dff | ||
|
||
return c | ||
} | ||
|
||
// IsPositive returns true if coin amount is positive. | ||
func (c Coin) IsPositive() bool { | ||
return c.Amount > 0 | ||
} | ||
|
||
// IsNegative returns true if the coin amount is negative and false otherwise. | ||
func (c Coin) IsNegative() bool { | ||
return c.Amount < 0 | ||
} | ||
|
||
// IsZero returns true if the amount of given coin is zero | ||
func (c Coin) IsZero() bool { | ||
return c.Amount == 0 | ||
} | ||
|
||
func mustMatchDenominations(denomA, denomB string) { | ||
if denomA != denomB { | ||
panic("incompatible coin denominations: " + denomA + ", " + denomB) | ||
} | ||
} | ||
|
||
// Coins is a set of Coin, one per currency | ||
type Coins []Coin | ||
|
||
// NewCoins returns a new set of Coins given one or more Coins | ||
// Consolidates any denom duplicates into one, keeping the properties of a mathematical set | ||
func NewCoins(coins ...Coin) Coins { | ||
coinMap := make(map[string]int64) | ||
|
||
for _, coin := range coins { | ||
if currentAmount, exists := coinMap[coin.Denom]; exists { | ||
var ok bool | ||
if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { | ||
panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) | ||
} | ||
} else { | ||
coinMap[coin.Denom] = coin.Amount | ||
} | ||
} | ||
|
||
var setCoins Coins | ||
for denom, amount := range coinMap { | ||
setCoins = append(setCoins, NewCoin(denom, amount)) | ||
} | ||
|
||
return setCoins | ||
} | ||
|
||
// String returns the string representation of Coins | ||
func (cz Coins) String() string { | ||
if len(cz) == 0 { | ||
return "" | ||
} | ||
|
||
res := "" | ||
for i, c := range cz { | ||
if i > 0 { | ||
res += "," | ||
} | ||
res += c.String() | ||
} | ||
|
||
return res | ||
} | ||
|
||
// AmountOf returns the amount of a specific coin from the Coins set | ||
func (cz Coins) AmountOf(denom string) int64 { | ||
for _, c := range cz { | ||
if c.Denom == denom { | ||
return c.Amount | ||
} | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
// Add adds a Coin to the Coins set | ||
func (cz Coins) Add(b Coins) Coins { | ||
c := Coins{} | ||
for _, ac := range cz { | ||
bc := b.AmountOf(ac.Denom) | ||
ac.Amount += bc | ||
c = append(c, ac) | ||
} | ||
|
||
for _, bc := range b { | ||
cc := c.AmountOf(bc.Denom) | ||
if cc == 0 { | ||
c = append(c, bc) | ||
} | ||
} | ||
|
||
return c | ||
} | ||
|
||
// expandNative expands for usage within natively bound functions. | ||
func (cz Coins) expandNative() (denoms []string, amounts []int64) { | ||
denoms = make([]string, len(cz)) | ||
amounts = make([]int64, len(cz)) | ||
for i, coin := range cz { | ||
denoms[i] = coin.Denom | ||
amounts[i] = coin.Amount | ||
} | ||
|
||
return denoms, amounts | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package bech32 | ||
|
||
// ConvertBits converts a byte slice where each byte is encoding fromBits bits, | ||
// to a byte slice where each byte is encoding toBits bits. | ||
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { | ||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { | ||
return nil, ErrInvalidBitGroups{} | ||
} | ||
|
||
// Determine the maximum size the resulting array can have after base | ||
// conversion, so that we can size it a single time. This might be off | ||
// by a byte depending on whether padding is used or not and if the input | ||
// data is a multiple of both fromBits and toBits, but we ignore that and | ||
// just size it to the maximum possible. | ||
maxSize := len(data)*int(fromBits)/int(toBits) + 1 | ||
|
||
// The final bytes, each byte encoding toBits bits. | ||
regrouped := make([]byte, 0, maxSize) | ||
|
||
// Keep track of the next byte we create and how many bits we have | ||
// added to it out of the toBits goal. | ||
nextByte := byte(0) | ||
filledBits := uint8(0) | ||
|
||
for _, b := range data { | ||
|
||
// Discard unused bits. | ||
b <<= 8 - fromBits | ||
|
||
// How many bits remaining to extract from the input data. | ||
remFromBits := fromBits | ||
for remFromBits > 0 { | ||
// How many bits remaining to be added to the next byte. | ||
remToBits := toBits - filledBits | ||
|
||
// The number of bytes to next extract is the minimum of | ||
// remFromBits and remToBits. | ||
toExtract := remFromBits | ||
if remToBits < toExtract { | ||
toExtract = remToBits | ||
} | ||
|
||
// Add the next bits to nextByte, shifting the already | ||
// added bits to the left. | ||
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract)) | ||
|
||
// Discard the bits we just extracted and get ready for | ||
// next iteration. | ||
b <<= toExtract | ||
remFromBits -= toExtract | ||
filledBits += toExtract | ||
|
||
// If the nextByte is completely filled, we add it to | ||
// our regrouped bytes and start on the next byte. | ||
if filledBits == toBits { | ||
regrouped = append(regrouped, nextByte) | ||
filledBits = 0 | ||
nextByte = 0 | ||
} | ||
} | ||
} | ||
|
||
// We pad any unfinished group if specified. | ||
if pad && filledBits > 0 { | ||
nextByte <<= toBits - filledBits | ||
regrouped = append(regrouped, nextByte) | ||
filledBits = 0 | ||
nextByte = 0 | ||
} | ||
|
||
// Any incomplete group must be <= 4 bits, and all zeroes. | ||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) { | ||
return nil, ErrInvalidIncompleteGroup{} | ||
} | ||
|
||
return regrouped, nil | ||
} | ||
|
||
func Encode(prefix string, data []byte) (string, error) { | ||
converted, err := ConvertBits(data, 8, 5, true) | ||
if err != nil { | ||
return "", errors.Wrap(err, "encoding bech32 failed") | ||
} | ||
return bech32.Encode(hrp, converted) | ||
} | ||
|
||
func Decode(s string) (prefix string, data []byte, err error) { | ||
hrp, data, err := bech32.DecodeNoLimit(bech) | ||
if err != nil { | ||
return "", nil, errors.Wrap(err, "decoding bech32 failed") | ||
} | ||
converted, err := ConvertBits(data, 5, 8, false) | ||
if err != nil { | ||
return "", nil, errors.Wrap(err, "decoding bech32 failed") | ||
} | ||
return hrp, converted, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Package runtime contains some runtime information, which is used by packages | ||
// like "chain" which cannot import "runtime" to avoid circular dependencies. | ||
package runtime | ||
|
||
// IsTesting returns whether the caller's caller is a function within a | ||
// _test.gno or a _filetest.gno file. | ||
func IsTesting() bool // injected | ||
|
||
// IsRuntime returns whether the caller's caller is the "runtime" package. | ||
func IsRuntime() bool // injected |
Oops, something went wrong.