-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathapp-deploy.ts
353 lines (335 loc) · 14.6 KB
/
app-deploy.ts
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import algosdk, { Address } from 'algosdk'
import { compileTeal, getAppOnCompleteAction } from './app'
import { _getAppArgsForABICall, _getBoxReference } from './transaction/legacy-bridge'
import { getSenderAddress, getSenderTransactionSigner } from './transaction/transaction'
import { AlgorandClientTransactionSender } from './types/algorand-client-transaction-sender'
import {
ABIReturn,
APP_DEPLOY_NOTE_DAPP,
AppCompilationResult,
AppDeployMetadata,
AppDeploymentParams,
AppLookup,
AppMetadata,
CompiledTeal,
TealTemplateParams,
} from './types/app'
import { AppDeployer } from './types/app-deployer'
import { AppManager, BoxReference } from './types/app-manager'
import { AssetManager } from './types/asset-manager'
import {
AppCreateMethodCall,
AppCreateParams,
AppDeleteMethodCall,
AppDeleteParams,
AppUpdateMethodCall,
AppUpdateParams,
TransactionComposer,
} from './types/composer'
import { Arc2TransactionNote, ConfirmedTransactionResult, ConfirmedTransactionResults, SendTransactionFrom } from './types/transaction'
import Algodv2 = algosdk.Algodv2
import Indexer = algosdk.Indexer
import modelsv2 = algosdk.modelsv2
/**
* @deprecated Use `algorand.appDeployer.deploy` instead.
*
* Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator account, including deploy-time template placeholder substitutions.
*
* To understand the architecture decisions behind this functionality please see https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md
*
* **Note:** When using the return from this function be sure to check `operationPerformed` to get access to various return properties like `transaction`, `confirmation` and `deleteResult`.
*
* **Note:** if there is a breaking state schema change to an existing app (and `onSchemaBreak` is set to `'replace'`) the existing app will be deleted and re-created.
*
* **Note:** if there is an update (different TEAL code) to an existing app (and `onUpdate` is set to `'replace'`) the existing app will be deleted and re-created.
* @param deployment The arguments to control the app deployment
* @param algod An algod client
* @param indexer An indexer client, needed if `existingDeployments` not passed in
* @returns The app reference of the new/existing app
*/
export async function deployApp(
deployment: AppDeploymentParams,
algod: Algodv2,
indexer?: Indexer,
): Promise<
Partial<AppCompilationResult> &
(
| (ConfirmedTransactionResults & AppMetadata & { return?: ABIReturn; operationPerformed: 'create' | 'update' })
| (ConfirmedTransactionResults &
AppMetadata & {
return?: ABIReturn
deleteReturn?: ABIReturn
deleteResult: ConfirmedTransactionResult
operationPerformed: 'replace'
})
| (AppMetadata & { operationPerformed: 'nothing' })
)
> {
const appManager = new AppManager(algod)
const newGroup = () =>
new TransactionComposer({
algod,
getSigner: () => getSenderTransactionSigner(deployment.from),
getSuggestedParams: async () =>
deployment.transactionParams ? { ...deployment.transactionParams } : await algod.getTransactionParams().do(),
appManager,
})
const deployer = new AppDeployer(
appManager,
new AlgorandClientTransactionSender(newGroup, new AssetManager(algod, newGroup), appManager),
indexer,
)
const createParams = {
approvalProgram: deployment.approvalProgram,
clearStateProgram: deployment.clearStateProgram,
sender: getSenderAddress(deployment.from),
accountReferences: deployment.createArgs?.accounts?.map((a) => (typeof a === 'string' ? a : algosdk.encodeAddress(a.publicKey))),
appReferences: deployment.createArgs?.apps?.map((a) => BigInt(a)),
assetReferences: deployment.createArgs?.assets?.map((a) => BigInt(a)),
boxReferences: deployment.createArgs?.boxes
?.map(_getBoxReference)
?.map((r) => ({ appId: BigInt(r.appIndex), name: r.name }) satisfies BoxReference),
lease: deployment.createArgs?.lease,
rekeyTo: deployment.createArgs?.rekeyTo ? getSenderAddress(deployment.createArgs?.rekeyTo) : undefined,
staticFee: deployment.fee,
maxFee: deployment.maxFee,
extraProgramPages: deployment.schema.extraPages,
onComplete: getAppOnCompleteAction(deployment.createOnCompleteAction) as Exclude<
algosdk.OnApplicationComplete,
algosdk.OnApplicationComplete.ClearStateOC
>,
schema: deployment.schema,
} satisfies Partial<AppCreateParams>
const updateParams = {
approvalProgram: deployment.approvalProgram,
clearStateProgram: deployment.clearStateProgram,
sender: getSenderAddress(deployment.from),
accountReferences: deployment.updateArgs?.accounts?.map((a) => (typeof a === 'string' ? a : algosdk.encodeAddress(a.publicKey))),
appReferences: deployment.updateArgs?.apps?.map((a) => BigInt(a)),
assetReferences: deployment.updateArgs?.assets?.map((a) => BigInt(a)),
boxReferences: deployment.updateArgs?.boxes
?.map(_getBoxReference)
?.map((r) => ({ appId: BigInt(r.appIndex), name: r.name }) satisfies BoxReference),
lease: deployment.updateArgs?.lease,
rekeyTo: deployment.updateArgs?.rekeyTo ? getSenderAddress(deployment.updateArgs?.rekeyTo) : undefined,
staticFee: deployment.fee,
maxFee: deployment.maxFee,
onComplete: algosdk.OnApplicationComplete.UpdateApplicationOC,
} satisfies Partial<AppUpdateParams>
const deleteParams = {
sender: getSenderAddress(deployment.from),
accountReferences: deployment.deleteArgs?.accounts?.map((a) => (typeof a === 'string' ? a : algosdk.encodeAddress(a.publicKey))),
appReferences: deployment.deleteArgs?.apps?.map((a) => BigInt(a)),
assetReferences: deployment.deleteArgs?.assets?.map((a) => BigInt(a)),
boxReferences: deployment.deleteArgs?.boxes
?.map(_getBoxReference)
?.map((r) => ({ appId: BigInt(r.appIndex), name: r.name }) satisfies BoxReference),
lease: deployment.deleteArgs?.lease,
rekeyTo: deployment.deleteArgs?.rekeyTo ? getSenderAddress(deployment.deleteArgs?.rekeyTo) : undefined,
staticFee: deployment.fee,
maxFee: deployment.maxFee,
onComplete: algosdk.OnApplicationComplete.DeleteApplicationOC,
} satisfies Partial<AppDeleteParams>
const encoder = new TextEncoder()
const result = await deployer.deploy({
createParams: deployment.createArgs?.method
? ({
...createParams,
method:
'txnCount' in deployment.createArgs.method ? deployment.createArgs.method : new algosdk.ABIMethod(deployment.createArgs.method),
args: (await _getAppArgsForABICall(deployment.createArgs, deployment.from)).methodArgs,
} satisfies AppCreateMethodCall)
: ({
...createParams,
args:
'appArgs' in (deployment?.createArgs ?? {})
? deployment.createArgs?.appArgs?.map((a) => (typeof a === 'string' ? encoder.encode(a) : a))
: undefined,
} satisfies AppCreateParams),
updateParams: deployment.updateArgs?.method
? ({
...updateParams,
method:
'txnCount' in deployment.updateArgs.method ? deployment.updateArgs.method : new algosdk.ABIMethod(deployment.updateArgs.method),
args: (await _getAppArgsForABICall(deployment.updateArgs, deployment.from)).methodArgs,
} satisfies Omit<AppUpdateMethodCall, 'appId'>)
: ({
...updateParams,
args:
'appArgs' in (deployment?.updateArgs ?? {})
? deployment.updateArgs?.appArgs?.map((a) => (typeof a === 'string' ? encoder.encode(a) : a))
: undefined,
} satisfies Omit<AppUpdateParams, 'appId'>),
deleteParams: deployment.deleteArgs?.method
? ({
...deleteParams,
method:
'txnCount' in deployment.deleteArgs.method ? deployment.deleteArgs.method : new algosdk.ABIMethod(deployment.deleteArgs.method),
args: (await _getAppArgsForABICall(deployment.deleteArgs, deployment.from)).methodArgs,
} satisfies Omit<AppDeleteMethodCall, 'appId'>)
: ({
...deleteParams,
args:
'appArgs' in (deployment?.deleteArgs ?? {})
? deployment.deleteArgs?.appArgs?.map((a) => (typeof a === 'string' ? encoder.encode(a) : a))
: undefined,
} satisfies Omit<AppDeleteParams, 'appId'>),
metadata: deployment.metadata,
deployTimeParams: deployment.deployTimeParams,
onSchemaBreak: deployment.onSchemaBreak,
onUpdate: deployment.onUpdate,
existingDeployments: deployment.existingDeployments
? {
creator: Address.fromString(deployment.existingDeployments.creator),
apps: Object.fromEntries(
Object.entries(deployment.existingDeployments.apps).map(([name, app]) => [
name,
{
...app,
appAddress: Address.fromString(app.appAddress),
appId: BigInt(app.appId),
createdRound: BigInt(app.createdRound),
updatedRound: BigInt(app.updatedRound),
},
]),
),
}
: undefined,
maxRoundsToWaitForConfirmation: deployment.maxRoundsToWaitForConfirmation,
populateAppCallResources: deployment.populateAppCallResources,
suppressLog: deployment.suppressLog,
})
return {
...result,
appAddress: result.appAddress.toString(),
appId: Number(result.appId),
createdRound: Number(result.createdRound),
updatedRound: Number(result.updatedRound),
}
}
/**
* @deprecated Use `before.numByteSlice < after.numByteSlice || before.numUint < after.numUint` instead.
*
* Returns true is there is a breaking change in the application state schema from before to after.
* i.e. if the schema becomes larger, since applications can't ask for more schema after creation.
* Otherwise, there is no error, the app just doesn't store data in the extra schema :(
*
* @param before The existing schema
* @param after The new schema
* @returns Whether or not there is a breaking change
*/
export function isSchemaIsBroken(before: modelsv2.ApplicationStateSchema, after: modelsv2.ApplicationStateSchema) {
return before.numByteSlice < after.numByteSlice || before.numUint < after.numUint
}
/**
* @deprecated Use `algorand.appDeployer.getCreatorAppsByName` instead.
*
* Returns a lookup of name => app metadata (id, address, ...metadata) for all apps created by the given account that have an `AppDeployNote` in the transaction note of the creation transaction.
*
* **Note:** It's recommended this is only called once and then stored since it's a somewhat expensive operation (multiple indexer calls).
*
* @param creatorAccount The account (with private key loaded) or string address of an account that is the creator of the apps you want to search for
* @param indexer An indexer client
* @returns A name-based lookup of the app information (id, address)
*/
export async function getCreatorAppsByName(creatorAccount: SendTransactionFrom | string, indexer: Indexer): Promise<AppLookup> {
const lookup = await new AppDeployer(undefined!, undefined!, indexer).getCreatorAppsByName(getSenderAddress(creatorAccount))
return {
creator: lookup.creator.toString(),
apps: Object.fromEntries(
Object.entries(lookup.apps).map(([name, app]) => [
name,
{
...app,
appAddress: app.appAddress.toString(),
appId: Number(app.appId),
createdRound: Number(app.createdRound),
updatedRound: Number(app.updatedRound),
},
]),
),
}
}
/**
* @deprecated Use `{ dAppName: APP_DEPLOY_NOTE_DAPP, data: metadata, format: 'j' }` instead.
*
* Return the transaction note for an app deployment.
* @param metadata The metadata of the deployment
* @returns The transaction note as a utf-8 string
*/
export function getAppDeploymentTransactionNote(metadata: AppDeployMetadata): Arc2TransactionNote {
return {
dAppName: APP_DEPLOY_NOTE_DAPP,
data: metadata,
format: 'j',
}
}
/**
* @deprecated Use `AppManager.replaceTealTemplateDeployTimeControlParams` instead
*
* Replaces deploy-time deployment control parameters within the given teal code.
*
* * `TMPL_UPDATABLE` for updatability / immutability control
* * `TMPL_DELETABLE` for deletability / permanence control
*
* Note: If these values are not undefined, but the corresponding `TMPL_*` value
* isn't in the teal code it will throw an exception.
*
* @param tealCode The TEAL code to substitute
* @param params The deploy-time deployment control parameter value to replace
* @returns The replaced TEAL code
*/
export function replaceDeployTimeControlParams(tealCode: string, params: { updatable?: boolean; deletable?: boolean }) {
return AppManager.replaceTealTemplateDeployTimeControlParams(tealCode, params)
}
/**
* @deprecated Use `AppManager.replaceTealTemplateParams` instead
*
* Performs template substitution of a teal file.
*
* Looks for `TMPL_{parameter}` for template replacements.
*
* @param tealCode The TEAL logic to compile
* @param templateParams Any parameters to replace in the .teal file before compiling
* @returns The TEAL code with replacements
*/
export function performTemplateSubstitution(tealCode: string, templateParams?: TealTemplateParams) {
return AppManager.replaceTealTemplateParams(tealCode, templateParams)
}
/**
* @deprecated Use `algorand.appManager.compileTealTemplate` instead.
*
* Performs template substitution of a teal file and compiles it, returning the compiled result.
*
* Looks for `TMPL_{parameter}` for template replacements.
*
* @param tealCode The TEAL logic to compile
* @param algod An algod client
* @param templateParams Any parameters to replace in the .teal file before compiling
* @param deploymentMetadata The deployment metadata the app will be deployed with
* @returns The information about the compiled code
*/
export async function performTemplateSubstitutionAndCompile(
tealCode: string,
algod: Algodv2,
templateParams?: TealTemplateParams,
deploymentMetadata?: AppDeployMetadata,
): Promise<CompiledTeal> {
tealCode = stripTealComments(tealCode)
tealCode = performTemplateSubstitution(tealCode, templateParams)
if (deploymentMetadata) {
tealCode = replaceDeployTimeControlParams(tealCode, deploymentMetadata)
}
return await compileTeal(tealCode, algod)
}
/**
* @deprecated Use `AppManager.stripTealComments` instead.
*
* Remove comments from TEAL Code
*
* @param tealCode The TEAL logic to compile
* @returns The TEAL without comments
*/
export function stripTealComments(tealCode: string) {
return AppManager.stripTealComments(tealCode)
}