summaryrefslogtreecommitdiff
path: root/data/basics/teal.go
blob: 412de5902b5af67c8018f33ff457422f6262f152 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package basics

import (
	"encoding/hex"
	"fmt"

	"github.com/algorand/go-algorand/config"
	"golang.org/x/exp/maps"
)

// DeltaAction is an enum of actions that may be performed when applying a
// delta to a TEAL key/value store
type DeltaAction uint64

const (
	// SetBytesAction indicates that a TEAL byte slice should be stored at a key
	SetBytesAction DeltaAction = 1

	// SetUintAction indicates that a Uint should be stored at a key
	SetUintAction DeltaAction = 2

	// DeleteAction indicates that the value for a particular key should be deleted
	DeleteAction DeltaAction = 3
)

// ValueDelta links a DeltaAction with a value to be set
type ValueDelta struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	Action DeltaAction `codec:"at"`
	Bytes  string      `codec:"bs,allocbound=config.MaxAppBytesValueLen"`
	Uint   uint64      `codec:"ui"`
}

// ToTealValue converts a ValueDelta into a TealValue if possible, and returns
// ok = false if the conversion is not possible.
func (vd *ValueDelta) ToTealValue() (value TealValue, ok bool) {
	switch vd.Action {
	case SetBytesAction:
		value.Type = TealBytesType
		value.Bytes = vd.Bytes
		ok = true
	case SetUintAction:
		value.Type = TealUintType
		value.Uint = vd.Uint
		ok = true
	case DeleteAction:
		ok = false
	default:
		ok = false
	}
	return value, ok
}

// StateDelta is a map from key/value store keys to ValueDeltas, indicating
// what should happen for that key
//
//msgp:allocbound StateDelta config.MaxStateDeltaKeys,config.MaxAppBytesKeyLen
type StateDelta map[string]ValueDelta

// Equal checks whether two StateDeltas are equal. We don't check for nilness
// equality because an empty map will encode/decode as nil. So if our generated
// map is empty but not nil, we want to equal a decoded nil off the wire.
func (sd StateDelta) Equal(o StateDelta) bool {
	return maps.Equal(sd, o)
}

// Valid checks whether the keys and values in a StateDelta conform to the
// consensus parameters' maximum lengths
func (sd StateDelta) Valid(proto *config.ConsensusParams) error {
	if len(sd) > 0 && proto.MaxAppKeyLen == 0 {
		return fmt.Errorf("delta not empty, but proto.MaxAppKeyLen is 0 (why did we make a delta?)")
	}
	for key, delta := range sd {
		if len(key) > proto.MaxAppKeyLen {
			return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), proto.MaxAppKeyLen)
		}
		switch delta.Action {
		case SetBytesAction:
			if len(delta.Bytes) > proto.MaxAppBytesValueLen {
				return fmt.Errorf("value too long for key 0x%x: length was %d", key, len(delta.Bytes))
			}
			if sum := len(key) + len(delta.Bytes); sum > proto.MaxAppSumKeyValueLens {
				return fmt.Errorf("key/value total too long for key 0x%x: sum was %d", key, sum)
			}
		case SetUintAction:
		case DeleteAction:
		default:
			return fmt.Errorf("unknown delta action: %v", delta.Action)
		}
	}
	return nil
}

// StateSchema sets maximums on the number of each type that may be stored
type StateSchema struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	NumUint      uint64 `codec:"nui"`
	NumByteSlice uint64 `codec:"nbs"`
}

// AddSchema adds two StateSchemas together
func (sm StateSchema) AddSchema(osm StateSchema) (out StateSchema) {
	out.NumUint = AddSaturate(sm.NumUint, osm.NumUint)
	out.NumByteSlice = AddSaturate(sm.NumByteSlice, osm.NumByteSlice)
	return
}

// SubSchema subtracts one StateSchema from another
func (sm StateSchema) SubSchema(osm StateSchema) (out StateSchema) {
	out.NumUint = SubSaturate(sm.NumUint, osm.NumUint)
	out.NumByteSlice = SubSaturate(sm.NumByteSlice, osm.NumByteSlice)
	return
}

// NumEntries counts the total number of values that may be stored for particular schema
func (sm StateSchema) NumEntries() (tot uint64) {
	tot = AddSaturate(tot, sm.NumUint)
	tot = AddSaturate(tot, sm.NumByteSlice)
	return tot
}

// MinBalance computes the MinBalance requirements for a StateSchema based on
// the consensus parameters
func (sm StateSchema) MinBalance(proto *config.ConsensusParams) (res MicroAlgos) {
	// Flat cost for each key/value pair
	flatCost := MulSaturate(proto.SchemaMinBalancePerEntry, sm.NumEntries())

	// Cost for uints
	uintCost := MulSaturate(proto.SchemaUintMinBalance, sm.NumUint)

	// Cost for byte slices
	bytesCost := MulSaturate(proto.SchemaBytesMinBalance, sm.NumByteSlice)

	// Sum the separate costs
	var min uint64
	min = AddSaturate(min, flatCost)
	min = AddSaturate(min, uintCost)
	min = AddSaturate(min, bytesCost)

	res.Raw = min
	return res
}

// TealType is an enum of the types in a TEAL program: Bytes and Uint
type TealType uint64

const (
	// TealBytesType represents the type of byte slice in a TEAL program
	TealBytesType TealType = 1

	// TealUintType represents the type of uint in a TEAL program
	TealUintType TealType = 2
)

func (tt TealType) String() string {
	switch tt {
	case TealBytesType:
		return "b"
	case TealUintType:
		return "u"
	}
	return "?"
}

// TealValue contains type information and a value, representing a value in a
// TEAL program
type TealValue struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	Type  TealType `codec:"tt"`
	Bytes string   `codec:"tb"`
	Uint  uint64   `codec:"ui"`
}

// ToValueDelta creates ValueDelta from TealValue
func (tv *TealValue) ToValueDelta() (vd ValueDelta) {
	if tv.Type == TealUintType {
		vd.Action = SetUintAction
		vd.Uint = tv.Uint
	} else {
		vd.Action = SetBytesAction
		vd.Bytes = tv.Bytes
	}
	return
}

func (tv *TealValue) String() string {
	if tv.Type == TealBytesType {
		return hex.EncodeToString([]byte(tv.Bytes))
	}
	return fmt.Sprintf("%d", tv.Uint)
}

// TealKeyValue represents a key/value store for use in an application's
// LocalState or GlobalState
//
//msgp:allocbound TealKeyValue EncodedMaxKeyValueEntries,config.MaxAppBytesKeyLen
type TealKeyValue map[string]TealValue

// Clone returns a copy of a TealKeyValue that may be modified without
// affecting the original
func (tk TealKeyValue) Clone() TealKeyValue {
	return maps.Clone(tk)
}

// ToStateSchema calculates the number of each value type in a TealKeyValue and
// represents the result as a StateSchema
func (tk TealKeyValue) ToStateSchema() (schema StateSchema, err error) {
	for _, value := range tk {
		switch value.Type {
		case TealBytesType:
			schema.NumByteSlice++
		case TealUintType:
			schema.NumUint++
		default:
			err = fmt.Errorf("unknown type %v", value.Type)
			return StateSchema{}, err
		}
	}
	return schema, nil
}