Skip to content

Commit

Permalink
feat: re-organize std
Browse files Browse the repository at this point in the history
  • Loading branch information
thehowl committed Jun 24, 2024
1 parent ea1ff3f commit ef3b264
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 0 deletions.
80 changes: 80 additions & 0 deletions gnovm/stdlibs/chain/chain.gno
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)
}
183 changes: 183 additions & 0 deletions gnovm/stdlibs/chain/coins.gno
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
}
97 changes: 97 additions & 0 deletions gnovm/stdlibs/crypto/bech32/bech32.gno
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
}
10 changes: 10 additions & 0 deletions gnovm/stdlibs/internal/runtime/runtime.gno
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
Loading

0 comments on commit ef3b264

Please sign in to comment.