Skip to content

Commit 702e43b

Browse files
committed
wip: ts-based ref checker
1 parent ab0d526 commit 702e43b

10 files changed

+225
-75
lines changed

examples/reti/artifacts/StakingPool.approval.teal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,7 +1668,7 @@ claimTokens:
16681668
// applicationID: AppID.fromUint64(this.creatingValidatorContractAppId.value),
16691669
// methodArgs: [
16701670
// { id: this.validatorId.value, poolId: this.poolId.value, poolAppId: this.app.id },
1671-
// staker,
1671+
// clone(staker),
16721672
// 0, // no algo removed
16731673
// amountRewardTokenRemoved,
16741674
// false, // staker isn't being removed.
@@ -1689,7 +1689,7 @@ claimTokens:
16891689
// examples/reti/stakingPool.algo.ts:435
16901690
// methodArgs: [
16911691
// { id: this.validatorId.value, poolId: this.poolId.value, poolAppId: this.app.id },
1692-
// staker,
1692+
// clone(staker),
16931693
// 0, // no algo removed
16941694
// amountRewardTokenRemoved,
16951695
// false, // staker isn't being removed.

examples/reti/artifacts/StakingPool.arc32.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

examples/reti/artifacts/StakingPool.arc56.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

examples/reti/artifacts/ValidatorRegistry.approval.teal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ getPools:
530530

531531
*if0_end:
532532
// examples/reti/validatorRegistry.algo.ts:325
533-
// retData.push(poolSet[i])
533+
// retData.push(clone(poolSet[i]))
534534
frame_dig 0 // retData: PoolInfo[]
535535
frame_dig 1 // poolSet: (uint64,uint16,uint64)[24]
536536
frame_dig 2 // i: uint64
@@ -933,7 +933,7 @@ getStakedPoolsForAccount:
933933

934934
// *if4_consequent
935935
// examples/reti/validatorRegistry.algo.ts:390
936-
// retData.push(poolSet[i])
936+
// retData.push(clone(poolSet[i]))
937937
frame_dig 0 // retData: ValidatorPoolKey[]
938938
frame_dig 1 // poolSet: (uint64,uint64,uint64)[6]
939939
frame_dig 2 // i: uint64

examples/reti/artifacts/ValidatorRegistry.arc32.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

examples/reti/artifacts/ValidatorRegistry.arc56.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

examples/reti/stakingPool.algo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ export class StakingPool extends Contract {
434434
applicationID: AppID.fromUint64(this.creatingValidatorContractAppId.value),
435435
methodArgs: [
436436
{ id: this.validatorId.value, poolId: this.poolId.value, poolAppId: this.app.id },
437-
staker,
437+
clone(staker),
438438
0, // no algo removed
439439
amountRewardTokenRemoved,
440440
false, // staker isn't being removed.

examples/reti/validatorRegistry.algo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export class ValidatorRegistry extends Contract {
322322
// reached end of list... we don't replace values here because pools can't be removed
323323
break;
324324
}
325-
retData.push(poolSet[i]);
325+
retData.push(clone(poolSet[i]));
326326
}
327327
return retData;
328328
}
@@ -387,7 +387,7 @@ export class ValidatorRegistry extends Contract {
387387
const poolSet = clone(this.stakerPoolSet(staker).value);
388388
for (let i = 0; i < poolSet.length; i += 1) {
389389
if (poolSet[i].id !== 0) {
390-
retData.push(poolSet[i]);
390+
retData.push(clone(poolSet[i]));
391391
}
392392
}
393393
return retData;

src/lib/compiler.ts

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import langspec from '../static/langspec.json';
1414
import { VERSION } from '../version';
1515
import { optimizeTeal } from './optimize';
1616
import { type ARC56Contract, type StructField } from '../types/arc56.d';
17+
import { checkRefs } from './ref_checker';
1718

1819
const MULTI_OUTPUT_TYPES = ['split uint128', 'divmodw output', 'vrf return values', 'ecdsa pubkey'];
1920

@@ -116,6 +117,42 @@ type Event = {
116117
argTupleType: TypeInfo;
117118
};
118119

120+
/**
121+
*
122+
* @param node The top level node to process
123+
* @param chain The existing expression chain to add to
124+
* @returns The base expression and reversed expression chain `this.txn.sender` ->
125+
* `{ chain: [this.txn, this.txn.sender], base: [this] }`
126+
*/
127+
export function getExpressionChain(
128+
node: ExpressionChainNode,
129+
chain: ExpressionChainNode[] = []
130+
): { chain: ExpressionChainNode[]; base: ts.Node } {
131+
chain.push(node);
132+
133+
/**
134+
* The expression on the given node
135+
* `this.txn.sender` -> `this.txn`
136+
*/
137+
let expr: ts.Expression = node.getExpression();
138+
139+
/* this.txn.applicationArgs! -> this.txn.applicationArgs */
140+
if (expr.isKind(ts.SyntaxKind.NonNullExpression)) {
141+
expr = expr.getExpression();
142+
}
143+
144+
if (
145+
expr.isKind(ts.SyntaxKind.ElementAccessExpression) ||
146+
expr.isKind(ts.SyntaxKind.PropertyAccessExpression) ||
147+
expr.isKind(ts.SyntaxKind.CallExpression)
148+
) {
149+
return getExpressionChain(expr, chain);
150+
}
151+
152+
chain.reverse();
153+
return { base: expr, chain };
154+
}
155+
119156
async function getSourceMap(vlqMappings: string) {
120157
const pcList = vlqMappings.split(';').map((m: string) => {
121158
const decoded = vlq.decode(m);
@@ -1088,23 +1125,6 @@ export default class Compiler {
10881125
*/
10891126
private topLevelNode!: ts.Node;
10901127

1091-
private mutableRefCheck(e: ts.Node) {
1092-
const typeInfo = this.getTypeInfo(e.getType());
1093-
if (typeInfo.kind !== 'base') {
1094-
if (
1095-
!(
1096-
e.isKind(ts.SyntaxKind.CallExpression) ||
1097-
e.isKind(ts.SyntaxKind.ArrayLiteralExpression) ||
1098-
e.isKind(ts.SyntaxKind.ObjectLiteralExpression)
1099-
)
1100-
) {
1101-
throw Error(
1102-
`Cannot have multiple multiple references to the same object. Use clone to create a deep copy: clone(${e.getText()})`
1103-
);
1104-
}
1105-
}
1106-
}
1107-
11081128
private getTypeInfo(type: ts.Type<ts.ts.Type>): TypeInfo {
11091129
if (type.getText() === 'SplitUint128') return { kind: 'base', type: 'split uint128' };
11101130
if (type.getText() === 'DivmodwOutput') return { kind: 'base', type: 'divmodw output' };
@@ -2056,8 +2076,6 @@ export default class Compiler {
20562076
if (!this.isDynamicArrayOfStaticType(this.lastType))
20572077
throw new Error('Cannot push to dynamic array of dynamic types');
20582078

2059-
this.mutableRefCheck(node.getArguments()[0]);
2060-
20612079
this.processNode(node.getArguments()[0]);
20622080
this.checkEncoding(node.getArguments()[0], this.lastType);
20632081
this.pushVoid(node, 'concat');
@@ -2857,6 +2875,10 @@ export default class Compiler {
28572875

28582876
// Go over all the files we are importing and check for number keywords
28592877
[sourceFile, ...importedFiles].forEach((f) => {
2878+
if (f === undefined) return;
2879+
2880+
checkRefs(f, path.relative(this.cwd, f.getFilePath()));
2881+
28602882
f?.getStatements().forEach((s) => {
28612883
const numberKeywords = s.getDescendantsOfKind(ts.SyntaxKind.NumberKeyword);
28622884

@@ -3655,10 +3677,6 @@ export default class Compiler {
36553677
const { typeHint } = this;
36563678
if (typeHint === undefined) throw Error('Type hint must be provided to process object or array');
36573679

3658-
elements.forEach((e) => {
3659-
this.mutableRefCheck(e);
3660-
});
3661-
36623680
if (
36633681
typeHint.kind === 'tuple' ||
36643682
typeHint.kind === 'object' ||
@@ -3720,42 +3738,6 @@ export default class Compiler {
37203738
this.processArrayElements(node.getElements(), node);
37213739
}
37223740

3723-
/**
3724-
*
3725-
* @param node The top level node to process
3726-
* @param chain The existing expression chain to add to
3727-
* @returns The base expression and reversed expression chain `this.txn.sender` ->
3728-
* `{ chain: [this.txn, this.txn.sender], base: [this] }`
3729-
*/
3730-
private getExpressionChain(
3731-
node: ExpressionChainNode,
3732-
chain: ExpressionChainNode[] = []
3733-
): { chain: ExpressionChainNode[]; base: ts.Node } {
3734-
chain.push(node);
3735-
3736-
/**
3737-
* The expression on the given node
3738-
* `this.txn.sender` -> `this.txn`
3739-
*/
3740-
let expr: ts.Expression = node.getExpression();
3741-
3742-
/* this.txn.applicationArgs! -> this.txn.applicationArgs */
3743-
if (expr.isKind(ts.SyntaxKind.NonNullExpression)) {
3744-
expr = expr.getExpression();
3745-
}
3746-
3747-
if (
3748-
expr.isKind(ts.SyntaxKind.ElementAccessExpression) ||
3749-
expr.isKind(ts.SyntaxKind.PropertyAccessExpression) ||
3750-
expr.isKind(ts.SyntaxKind.CallExpression)
3751-
) {
3752-
return this.getExpressionChain(expr, chain);
3753-
}
3754-
3755-
chain.reverse();
3756-
return { base: expr, chain };
3757-
}
3758-
37593741
private getAccessChain(node: ts.ElementAccessExpression, chain: ts.ElementAccessExpression[] = []) {
37603742
chain.push(node);
37613743

@@ -4250,8 +4232,6 @@ export default class Compiler {
42504232
!this.isDynamicType(this.storageProps[getStorageName(storageExpression)!].valueType);
42514233

42524234
if (newValue) {
4253-
this.mutableRefCheck(newValue);
4254-
42554235
if (newValue.getType().isNumberLiteral()) {
42564236
this.processNewValue(newValue, elem.type);
42574237
} else {
@@ -4362,8 +4342,6 @@ export default class Compiler {
43624342
}
43634343

43644344
if (newValue) {
4365-
this.mutableRefCheck(newValue);
4366-
43674345
if (this.isDynamicType(element.type)) {
43684346
if (element.parent?.arrayType !== 'tuple') {
43694347
throw new Error(
@@ -6344,7 +6322,7 @@ export default class Compiler {
63446322
}
63456323
}
63466324

6347-
const { base, chain } = this.getExpressionChain(node);
6325+
const { base, chain } = getExpressionChain(node);
63486326

63496327
if (base.isKind(ts.SyntaxKind.ParenthesizedExpression)) {
63506328
if (!base.getExpression().isKind(ts.SyntaxKind.BinaryExpression))

0 commit comments

Comments
 (0)