/
asa.ts
160 lines (150 loc) · 5.43 KB
/
asa.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
import { ASADefSchema, types } from "@algo-builder/web";
import { modelsv2 } from "algosdk";
import { existsSync } from "fs";
import path from "path";
import * as z from "zod";
import { RUNTIME_ERRORS } from "../errors/errors-list";
import { RuntimeError } from "../errors/runtime-errors";
import { parseZodError } from "../errors/validation-errors";
import { AccountMap, RuntimeAccountMap } from "../types";
import { getPathFromDirRecursive, loadFromYamlFileSilent } from "./files";
export const ASSETS_DIR = "assets";
/**
* Validates asset definitions and checks if opt-in acc names are present in network
* @param accounts AccountMap is the SDK account type, used in builder. RuntimeAccountMap is
* for AccountStore used in runtime (where we use maps instead of arrays in sdk structures).
* @param asaDef asset definition
* @param source
*/
export function validateOptInAccNames(
accounts: AccountMap | RuntimeAccountMap,
asaDef: types.ASADef,
source?: string
): void {
if (!asaDef.optInAccNames || asaDef.optInAccNames.length === 0) {
return;
}
for (const accName of asaDef.optInAccNames) {
if (!accounts.get(accName)) {
throw new RuntimeError(RUNTIME_ERRORS.ASA.PARAM_ERROR_NO_NAMED_OPT_IN_ACCOUNT, {
source: source,
optInAccName: accName,
});
}
}
}
/**
* Validate and parse each field of asset definition. `metadataHash`, if provided as a Buffer
* will be transformed into Uint8Array.
* @param asaDef asset definition
* @param source source of assetDef: asa.yaml file OR function deployASA
* @returns parsed asa definition
*/
export function parseASADef(asaDef: types.ASADef, source?: string): types.ASADef {
try {
if (asaDef.metadataHash && asaDef.metadataHash instanceof Buffer) {
asaDef.metadataHash = new Uint8Array(asaDef.metadataHash);
}
const parsedDef = ASADefSchema.parse(asaDef);
parsedDef.manager = parsedDef.manager !== "" ? parsedDef.manager : undefined;
parsedDef.reserve = parsedDef.reserve !== "" ? parsedDef.reserve : undefined;
parsedDef.freeze = parsedDef.freeze !== "" ? parsedDef.freeze : undefined;
parsedDef.clawback = parsedDef.clawback !== "" ? parsedDef.clawback : undefined;
parsedDef.defaultFrozen = parsedDef.defaultFrozen ?? false;
return parsedDef;
} catch (e) {
if (e instanceof z.ZodError) {
throw new RuntimeError(
RUNTIME_ERRORS.ASA.PARAM_PARSE_ERROR,
{
reason: parseZodError(e),
source: source,
},
e
);
}
throw e;
}
}
/**
* Override & validate ASA definition in asa.yaml using custom params passed via deployASA
* @param accounts accounts by name
* @param origDef source asset definition (in asa.yaml)
* @param newDef custom asset def params (passed during ASA deployment)
* @returns overriden asset definition. If custom params are empty, return source asa def
*/
export function overrideASADef(
accounts: AccountMap,
origDef: types.ASADef,
newDef?: Partial<types.ASADef>
): types.ASADef {
if (newDef === undefined) {
return origDef;
}
const source = "ASA deployment";
Object.assign(origDef, newDef);
origDef = parseASADef(origDef, source);
validateOptInAccNames(accounts, origDef, source);
return origDef;
}
/**
* Parses, overrides and validates asset defs map. Filaname parameter is used to
indicate an ASA definition source when reporting errors.
* @param asaDefs asset definitions to validate
* @param accounts map of string => account. AccountMap is the SDK account type,
* used in builder. RuntimeAccountMap is for AccountStore used in runtime
* (where we use maps instead of arrays in sdk structures).
* @param filename asa filename
*/
export function validateASADefs(
asaDefs: types.ASADefs,
accounts: AccountMap | RuntimeAccountMap,
filename: string
): types.ASADefs {
for (const name in asaDefs) {
asaDefs[name] = parseASADef(asaDefs[name], filename);
asaDefs[name].name = name; // save asa name in def as well
validateOptInAccNames(accounts, asaDefs[name], filename);
}
return asaDefs;
}
/**
* Loads, validates and returns asset definitions from the assets/asa.yaml file
* @param accounts map of string => account. AccountMap is the SDK account type,
* used in builder. RuntimeAccountMap is for AccountStore used in runtime
* (where we use maps instead of arrays in sdk structures).
*/
export function loadASAFile(accounts: AccountMap | RuntimeAccountMap): types.ASADefs {
let filePath;
if (!existsSync(ASSETS_DIR)) {
// to handle tests
filePath = path.join(ASSETS_DIR, "asa.yaml");
} else {
filePath = getPathFromDirRecursive(ASSETS_DIR, "asa.yaml", "") as string;
}
return validateASADefs(loadFromYamlFileSilent(filePath), accounts, filePath);
}
function isDefined(value: string | undefined): boolean {
return value !== undefined && value !== "";
}
/**
* Check and Change ASA fields
* @param fields Custom ASA fields
* @param asset Defined ASA fields
*/
export function checkAndSetASAFields(
fields: types.AssetModFields,
asset: modelsv2.AssetParams
): void {
for (const x of ["manager", "reserve", "freeze", "clawback"]) {
const customField = fields[x as keyof types.AssetModFields];
const asaField = asset[x as keyof types.AssetModFields];
// Check if custom field is set and defined and ASA field is blank field
if (isDefined(customField) && !isDefined(asaField)) {
throw new RuntimeError(RUNTIME_ERRORS.ASA.BLANK_ADDRESS_ERROR);
} else if (customField !== undefined && isDefined(asaField)) {
// Change if ASA field and custom field is defined
asset[x as keyof types.AssetModFields] = customField;
}
}
}