Search Apps Documentation Source Content File Folder Download Copy Actions Download

denom.gno

5.01 Kb · 188 lines
  1package transfer
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"strings"
  7
  8	"gno.land/p/aib/ibc/host"
  9	"gno.land/p/aib/ibc/types"
 10	"gno.land/p/nt/ufmt/v0"
 11	"gno.land/p/onbloc/json"
 12)
 13
 14// Denom holds the base denom of a Token and a trace of the chains it was sent
 15// through.
 16type Denom struct {
 17	// the base token denomination
 18	Base string
 19	// the trace of the token
 20	Trace []Hop
 21}
 22
 23// NewDenom creates a new Denom instance given the base denomination and a
 24// variable number of hops.
 25func NewDenom(base string, trace ...Hop) Denom {
 26	return Denom{
 27		Base:  base,
 28		Trace: trace,
 29	}
 30}
 31
 32// ValidateBasic performs a basic validation of the Denom fields.
 33func (d Denom) ValidateBasic() error {
 34	// NOTE: base denom validation cannot be performed as each chain may define
 35	// its own base denom validation
 36	if strings.TrimSpace(d.Base) == "" {
 37		return ufmt.Errorf("base denomination cannot be blank")
 38	}
 39
 40	for _, hop := range d.Trace {
 41		if err := hop.ValidateBasic(); err != nil {
 42			return ufmt.Errorf("invalid trace")
 43		}
 44	}
 45
 46	return nil
 47}
 48
 49// IsNative returns true if the denomination is native, thus containing no
 50// trace history.
 51func (d Denom) IsNative() bool {
 52	return len(d.Trace) == 0
 53}
 54
 55// Path returns the full denomination according to the ICS20 specification:
 56// trace + "/" + baseDenom
 57// If there exists no trace then the base denomination is returned.
 58func (d Denom) Path() string {
 59	if d.IsNative() {
 60		return d.Base
 61	}
 62
 63	var sb strings.Builder
 64	for _, t := range d.Trace {
 65		sb.WriteString(t.String())
 66		sb.WriteByte('/')
 67	}
 68	sb.WriteString(d.Base)
 69	return sb.String()
 70}
 71
 72// HasPrefix returns true if the first element of the trace of the denom
 73// matches the provided portId and clientId.
 74func (d Denom) HasPrefix(portID, clientID string) bool {
 75	// if the denom is native, then it is not prefixed by any port/channel pair
 76	if d.IsNative() {
 77		return false
 78	}
 79	return d.Trace[0].PortId == portID && d.Trace[0].ClientId == clientID
 80}
 81
 82// Hash returns the hex bytes of the SHA256 hash of the Denom fields using the
 83// following formula:
 84//
 85// hash = sha256(trace + "/" + baseDenom)
 86func (d Denom) Hash() []byte {
 87	hash := sha256.Sum256([]byte(d.Path()))
 88	return hash[:]
 89}
 90
 91func (d Denom) HashHex() string {
 92	return strings.ToUpper(hex.EncodeToString(d.Hash()))
 93}
 94
 95// IBCDenom a coin denomination for an ICS20 fungible token in the format
 96// 'ibc/{hash(trace + baseDenom)}'. If the trace is empty, it will return the
 97// base denomination.
 98func (d Denom) IBCDenom() string {
 99	if d.IsNative() {
100		return d.Base
101	}
102	return ufmt.Sprintf("%s/%s", denomPrefix, d.HashHex())
103}
104
105// Implements jsonpage.JSONRenderer
106func (d Denom) RenderJSON() *json.Node {
107	return json.ObjectNode("", map[string]*json.Node{
108		"base":  json.StringNode("", d.Base),
109		"path":  json.StringNode("", d.Path()),
110		"denom": json.StringNode("", d.IBCDenom()),
111	})
112}
113
114func (d Denom) MarshalJSON() []byte {
115	return []byte(d.RenderJSON().String())
116}
117
118// ExtractDenomFromPath returns the denom from the full path.
119func ExtractDenomFromPath(fullPath string) Denom {
120	denomSplit := strings.Split(fullPath, "/")
121
122	if denomSplit[0] == fullPath {
123		return Denom{
124			Base: fullPath,
125		}
126	}
127
128	var (
129		trace          []Hop
130		baseDenomSlice []string
131	)
132
133	length := len(denomSplit)
134	for i := 0; i < length; i += 2 {
135		// The IBC specification does not guarantee the expected format of the
136		// destination port or destination channel identifier. A short term
137		// solution to determine base denomination is to expect the channel
138		// identifier to be the one ibc-go specifies. A longer term solution is to
139		// separate the path and base denomination in the ICS20 packet. If an
140		// intermediate hop prefixes the full denom with a channel identifier
141		// format different from our own, the base denomination will be incorrectly
142		// parsed, but the token will continue to be treated correctly as an IBC
143		// denomination. The hash used to store the token internally on our chain
144		// will be the same value as the base denomination being correctly parsed.
145		if i < length-1 && length > 2 && types.IsValidClientID(denomSplit[i+1]) {
146			trace = append(trace, NewHop(denomSplit[i], denomSplit[i+1]))
147		} else {
148			baseDenomSlice = denomSplit[i:]
149			break
150		}
151	}
152
153	base := strings.Join(baseDenomSlice, "/")
154
155	return Denom{
156		Base:  base,
157		Trace: trace,
158	}
159}
160
161// Hop defines a port ID, client ID pair specifying a unique "hop" in a trace
162type Hop struct {
163	PortId   string
164	ClientId string
165}
166
167// NewHop creates a Hop with the given port ID and client ID.
168func NewHop(portID, clientID string) Hop {
169	return Hop{portID, clientID}
170}
171
172// ValidateBasic performs a basic validation of the Hop fields.
173func (h Hop) ValidateBasic() error {
174	if err := host.PortIdentifierValidator(h.PortId); err != nil {
175		return ufmt.Errorf("invalid hop source port ID %s", h.PortId)
176	}
177	if err := host.ClientIdentifierValidator(h.ClientId); err != nil {
178		return ufmt.Errorf("invalid hop source client ID %s", h.ClientId)
179	}
180
181	return nil
182}
183
184// String returns the Hop in the format:
185// <portID>/<clientID>
186func (h Hop) String() string {
187	return ufmt.Sprintf("%s/%s", h.PortId, h.ClientId)
188}