summaryrefslogtreecommitdiff
path: root/data/transactions/application.go
blob: db7037700bd3c4fc41fa255aebea5f5cea1645e0 (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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// 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 transactions

import (
	"fmt"

	"github.com/algorand/go-algorand/data/basics"
	"golang.org/x/exp/slices"
)

const (
	// encodedMaxApplicationArgs sets the allocation bound for the maximum
	// number of ApplicationArgs that a transaction decoded off of the wire
	// can contain. Its value is verified against consensus parameters in
	// TestEncodedAppTxnAllocationBounds
	encodedMaxApplicationArgs = 32

	// encodedMaxAccounts sets the allocation bound for the maximum number
	// of Accounts that a transaction decoded off of the wire can contain.
	// Its value is verified against consensus parameters in
	// TestEncodedAppTxnAllocationBounds
	encodedMaxAccounts = 32

	// encodedMaxForeignApps sets the allocation bound for the maximum
	// number of ForeignApps that a transaction decoded off of the wire can
	// contain. Its value is verified against consensus parameters in
	// TestEncodedAppTxnAllocationBounds
	encodedMaxForeignApps = 32

	// encodedMaxForeignAssets sets the allocation bound for the maximum
	// number of ForeignAssets that a transaction decoded off of the wire
	// can contain. Its value is verified against consensus parameters in
	// TestEncodedAppTxnAllocationBounds
	encodedMaxForeignAssets = 32

	// encodedMaxBoxes sets the allocation bound for the maximum
	// number of Boxes that a transaction decoded off of the wire
	// can contain. Its value is verified against consensus parameters in
	// TestEncodedAppTxnAllocationBounds
	encodedMaxBoxes = 32
)

// OnCompletion is an enum representing some layer 1 side effect that an
// ApplicationCall transaction will have if it is included in a block.
//
//go:generate stringer -type=OnCompletion -output=application_string.go
type OnCompletion uint64

const (
	// NoOpOC indicates that an application transaction will simply call its
	// ApprovalProgram
	NoOpOC OnCompletion = 0

	// OptInOC indicates that an application transaction will allocate some
	// LocalState for the application in the sender's account
	OptInOC OnCompletion = 1

	// CloseOutOC indicates that an application transaction will deallocate
	// some LocalState for the application from the user's account
	CloseOutOC OnCompletion = 2

	// ClearStateOC is similar to CloseOutOC, but may never fail. This
	// allows users to reclaim their minimum balance from an application
	// they no longer wish to opt in to. When an ApplicationCall
	// transaction's OnCompletion is ClearStateOC, the ClearStateProgram
	// executes instead of the ApprovalProgram
	ClearStateOC OnCompletion = 3

	// UpdateApplicationOC indicates that an application transaction will
	// update the ApprovalProgram and ClearStateProgram for the application
	UpdateApplicationOC OnCompletion = 4

	// DeleteApplicationOC indicates that an application transaction will
	// delete the AppParams for the application from the creator's balance
	// record
	DeleteApplicationOC OnCompletion = 5
)

// ApplicationCallTxnFields captures the transaction fields used for all
// interactions with applications
type ApplicationCallTxnFields struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	// ApplicationID is 0 when creating an application, and nonzero when
	// calling an existing application.
	ApplicationID basics.AppIndex `codec:"apid"`

	// OnCompletion specifies an optional side-effect that this transaction
	// will have on the balance record of the sender or the application's
	// creator. See the documentation for the OnCompletion type for more
	// information on each possible value.
	OnCompletion OnCompletion `codec:"apan"`

	// ApplicationArgs are arguments accessible to the executing
	// ApprovalProgram or ClearStateProgram.
	ApplicationArgs [][]byte `codec:"apaa,allocbound=encodedMaxApplicationArgs,maxtotalbytes=config.MaxAppTotalArgLen"`

	// Accounts are accounts whose balance records are accessible
	// by the executing ApprovalProgram or ClearStateProgram. To
	// access LocalState or an ASA balance for an account besides
	// the sender, that account's address must be listed here (and
	// since v4, the ForeignApp or ForeignAsset must also include
	// the app or asset id).
	Accounts []basics.Address `codec:"apat,allocbound=encodedMaxAccounts"`

	// ForeignApps are application IDs for applications besides
	// this one whose GlobalState (or Local, since v4) may be read
	// by the executing ApprovalProgram or ClearStateProgram.
	ForeignApps []basics.AppIndex `codec:"apfa,allocbound=encodedMaxForeignApps"`

	// Boxes are the boxes that can be accessed by this transaction (and others
	// in the same group). The Index in the BoxRef is the slot of ForeignApps
	// that the name is associated with (shifted by 1, so 0 indicates "current
	// app")
	Boxes []BoxRef `codec:"apbx,allocbound=encodedMaxBoxes"`

	// ForeignAssets are asset IDs for assets whose AssetParams
	// (and since v4, Holdings) may be read by the executing
	// ApprovalProgram or ClearStateProgram.
	ForeignAssets []basics.AssetIndex `codec:"apas,allocbound=encodedMaxForeignAssets"`

	// LocalStateSchema specifies the maximum number of each type that may
	// appear in the local key/value store of users who opt in to this
	// application. This field is only used during application creation
	// (when the ApplicationID field is 0),
	LocalStateSchema basics.StateSchema `codec:"apls"`

	// GlobalStateSchema specifies the maximum number of each type that may
	// appear in the global key/value store associated with this
	// application. This field is only used during application creation
	// (when the ApplicationID field is 0).
	GlobalStateSchema basics.StateSchema `codec:"apgs"`

	// ApprovalProgram is the stateful TEAL bytecode that executes on all
	// ApplicationCall transactions associated with this application,
	// except for those where OnCompletion is equal to ClearStateOC. If
	// this program fails, the transaction is rejected. This program may
	// read and write local and global state for this application.
	ApprovalProgram []byte `codec:"apap,allocbound=config.MaxAvailableAppProgramLen"`

	// ClearStateProgram is the stateful TEAL bytecode that executes on
	// ApplicationCall transactions associated with this application when
	// OnCompletion is equal to ClearStateOC. This program will not cause
	// the transaction to be rejected, even if it fails. This program may
	// read and write local and global state for this application.
	ClearStateProgram []byte `codec:"apsu,allocbound=config.MaxAvailableAppProgramLen"`

	// ExtraProgramPages specifies the additional app program len requested in pages.
	// A page is MaxAppProgramLen bytes. This field enables execution of app programs
	// larger than the default config, MaxAppProgramLen.
	ExtraProgramPages uint32 `codec:"apep,omitempty"`

	// If you add any fields here, remember you MUST modify the Empty
	// method below!
}

// BoxRef names a box by the slot
type BoxRef struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	Index uint64 `codec:"i"`
	Name  []byte `codec:"n,allocbound=config.MaxBytesKeyValueLen"`
}

// Empty indicates whether or not all the fields in the
// ApplicationCallTxnFields are zeroed out
func (ac *ApplicationCallTxnFields) Empty() bool {
	if ac.ApplicationID != 0 {
		return false
	}
	if ac.OnCompletion != 0 {
		return false
	}
	if ac.ApplicationArgs != nil {
		return false
	}
	if ac.Accounts != nil {
		return false
	}
	if ac.ForeignApps != nil {
		return false
	}
	if ac.ForeignAssets != nil {
		return false
	}
	if ac.Boxes != nil {
		return false
	}
	if ac.LocalStateSchema != (basics.StateSchema{}) {
		return false
	}
	if ac.GlobalStateSchema != (basics.StateSchema{}) {
		return false
	}
	if ac.ApprovalProgram != nil {
		return false
	}
	if ac.ClearStateProgram != nil {
		return false
	}
	if ac.ExtraProgramPages != 0 {
		return false
	}
	return true
}

// AddressByIndex converts an integer index into an address associated with the
// transaction. Index 0 corresponds to the transaction sender, and an index > 0
// corresponds to an offset into txn.Accounts. Returns an error if the index is
// not valid.
func (ac *ApplicationCallTxnFields) AddressByIndex(accountIdx uint64, sender basics.Address) (basics.Address, error) {
	// Index 0 always corresponds to the sender
	if accountIdx == 0 {
		return sender, nil
	}

	// An index > 0 corresponds to an offset into txn.Accounts. Check to
	// make sure the index is valid.
	if accountIdx > uint64(len(ac.Accounts)) {
		return basics.Address{}, fmt.Errorf("invalid Account reference %d", accountIdx)
	}

	// accountIdx must be in [1, len(ac.Accounts)]
	return ac.Accounts[accountIdx-1], nil
}

// IndexByAddress converts an address into an integer offset into [txn.Sender,
// txn.Accounts[0], ...], returning the index at the first match. It returns
// an error if there is no such match.
func (ac *ApplicationCallTxnFields) IndexByAddress(target basics.Address, sender basics.Address) (uint64, error) {
	// Index 0 always corresponds to the sender
	if target == sender {
		return 0, nil
	}

	// Otherwise we index into ac.Accounts
	if idx := slices.Index(ac.Accounts, target); idx != -1 {
		return uint64(idx) + 1, nil
	}

	return 0, fmt.Errorf("invalid Account reference %s", target)
}