Skip to content

Commit

Permalink
fix: Support emit calls which include an explicit signature
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanmenzel committed Jan 7, 2025
1 parent 0f5cfce commit bcc936b
Show file tree
Hide file tree
Showing 28 changed files with 2,524 additions and 1,294 deletions.
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dev:examples": "tsx src/cli.ts build examples --output-awst --output-awst-json",
"dev:approvals": "rimraf tests/approvals/out && tsx src/cli.ts build tests/approvals --dry-run",
"dev:expected-output": "tsx src/cli.ts build tests/expected-output --dry-run",
"dev:testing": "tsx src/cli.ts build tests/approvals/abi-decorators.algo.ts --output-awst --output-awst-json --output-ssa-ir --log-level=info --out-dir out/[name] --optimization-level=0",
"dev:testing": "tsx src/cli.ts build tests/approvals/arc-28-events.algo.ts --output-awst --output-awst-json --output-ssa-ir --log-level=info --out-dir out/[name] --optimization-level=0",
"audit": "better-npm-audit audit",
"format": "prettier --write .",
"lint": "eslint \"src/**/*.ts\"",
Expand All @@ -21,7 +21,7 @@
"build:3-build": "rollup -c --configPlugin typescript",
"build:4-copy-pkg-json": "tstk copy-package-json -c",
"build:5-copy-readme": "copyfiles ./README.md ./dist",
"test": "vitest run",
"test": "vitest run tests",
"test:approvals": "vitest run tests/approvals.spec.ts",
"test:expected-output": "vitest run tests/expected-output.spec.ts",
"test:coverage": "vitest run --coverage",
Expand Down Expand Up @@ -74,6 +74,7 @@
"vitest": "2.1.8"
},
"dependencies": {
"arcsecond": "^5.0.0",
"chalk": "^5.3.0",
"change-case": "^5.4.4",
"commander": "^12.1.0",
Expand Down
6 changes: 6 additions & 0 deletions src/awst/wtypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ export namespace wtypes {
nativeType: n <= 64 ? uint64WType : biguintWType,
arc4Name: arc4Name ?? `uint${n}`,
})
invariant(n >= 8n && n <= 512n, 'Invalid uint: n must be between 8 and 512')
invariant(n % 8n === 0n, 'Invalid uint: n must be multiple of 8')
this.n = n
}
}
Expand All @@ -242,6 +244,10 @@ export namespace wtypes {
nativeType: n <= 64 ? uint64WType : biguintWType,
arc4Name: `ufixed${n}x${m}`,
})

invariant(n >= 8n && n <= 512n, 'Invalid ufixed: n must be between 8 and 512')
invariant(n % 8n === 0n, 'Invalid ufixed: n must be multiple of 8')
invariant(m >= 0n && m <= 160n, 'Invalid ufixed: m must be between 0 and 160')
this.n = n
this.m = m
}
Expand Down
9 changes: 5 additions & 4 deletions src/awst_build/arc4-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
voidPType,
} from './ptypes'
import {
ARC4BooleanType,
arc4BooleanType,
ARC4EncodedType,
ARC4StringType,
arc4StringType,
ARC4StructType,
ARC4TupleType,
DynamicBytesType,
Expand Down Expand Up @@ -58,15 +58,16 @@ export function ptypeToArc4EncodedType(ptype: ObjectPType, sourceLocation: Sourc
export function ptypeToArc4EncodedType(ptype: PType, sourceLocation: SourceLocation): ARC4EncodedType
export function ptypeToArc4EncodedType(ptype: PType, sourceLocation: SourceLocation): ARC4EncodedType {
if (ptype instanceof ARC4EncodedType) return ptype
if (ptype.equals(boolPType)) return ARC4BooleanType
if (ptype.equals(boolPType)) return arc4BooleanType
if (ptype.equals(uint64PType)) return new UintNType({ n: 64n })
if (ptype.equals(biguintPType)) return new UintNType({ n: 512n })
if (ptype.equals(bytesPType)) return DynamicBytesType
if (ptype.equals(stringPType)) return ARC4StringType
if (ptype.equals(stringPType)) return arc4StringType
if (ptype instanceof NativeNumericType) {
throw new CodeError(numberPType.expressionMessage, { sourceLocation })
}
if (ptype instanceof TuplePType) return new ARC4TupleType({ types: ptype.items.map((i) => ptypeToArc4EncodedType(i, sourceLocation)) })

if (ptype instanceof ObjectPType)
return new ARC4StructType({
name: ptype.name,
Expand Down
73 changes: 64 additions & 9 deletions src/awst_build/eb/arc28/arc-28-emit-function-builder.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { nodeFactory } from '../../../awst/node-factory'
import type { Expression } from '../../../awst/nodes'
import type { SourceLocation } from '../../../awst/source-location'
import { InternalError } from '../../../errors'
import type { Expression, StringConstant } from '../../../awst/nodes'
import { SourceLocation } from '../../../awst/source-location'
import { CodeError, InternalError } from '../../../errors'
import { logger } from '../../../logger'
import { codeInvariant } from '../../../util'
import { Arc4ParseError, parseArc4Type } from '../../../util/arc4-signature-parser'
import { ptypeToArc4EncodedType } from '../../arc4-util'
import type { PType } from '../../ptypes'
import { arc28EmitFunction, ObjectPType, stringPType, voidPType } from '../../ptypes'
import { ARC4EncodedType, ARC4StructType } from '../../ptypes/arc4-types'
import { ARC4EncodedType, ARC4StructType, ARC4TupleType } from '../../ptypes/arc4-types'
import { instanceEb } from '../../type-registry'
import type { NodeBuilder } from '../index'
import type { InstanceBuilder, NodeBuilder } from '../index'
import { FunctionBuilder } from '../index'
import { requireStringConstant } from '../util'
import { parseFunctionArgs } from '../util/arg-parsing'
Expand All @@ -31,17 +32,28 @@ export class Arc28EmitFunctionBuilder extends FunctionBuilder {
})

if (nameOrObj.ptype.equals(stringPType)) {
const name = requireStringConstant(nameOrObj).value
const thisModule = nameOrObj.sourceLocation.file ?? ''

const fields: Record<string, ARC4EncodedType> = {}
const values = new Map<string, Expression>()

for (const [index, prop] of Object.entries(props)) {
const { name, propTypes } = parseEventName(nameOrObj)

for (const [index, prop] of props.entries()) {
const arc4Type = ptypeToArc4EncodedType(prop.ptype, prop.sourceLocation)

const expectedType = propTypes?.[index]
if (expectedType) {
codeInvariant(
expectedType.equals(arc4Type),
`Expected type ${expectedType} does not match actual type ${arc4Type}`,
prop.sourceLocation,
)
}

fields[index] = arc4Type
values.set(
index,
index.toString(),
prop.ptype instanceof ARC4EncodedType
? prop.resolve()
: nodeFactory.aRC4Encode({
Expand All @@ -51,9 +63,14 @@ export class Arc28EmitFunctionBuilder extends FunctionBuilder {
}),
)
}
if (propTypes && propTypes.length !== values.size) {
throw new CodeError(`Event signature length (${propTypes.length}) does not match number of provided values (${values.size}).`, {
sourceLocation: sourceLocation,
})
}

const structType = new ARC4StructType({
name,
name: name.value,
module: thisModule,
fields,
description: undefined,
Expand Down Expand Up @@ -107,3 +124,41 @@ function emitStruct(ptype: ARC4StructType, expression: Expression, sourceLocatio
voidPType,
)
}

function parseEventName(nameBuilder: InstanceBuilder): {
name: StringConstant
propTypes?: ARC4EncodedType[]
} {
const name = requireStringConstant(nameBuilder)
const parenthesisIndex = name.value.indexOf('(')
if (parenthesisIndex === -1) {
return {
name,
}
}
const signature = name.value.substring(parenthesisIndex)

try {
const signatureType = parseArc4Type(signature)
codeInvariant(signatureType instanceof ARC4TupleType, 'Event signature must be a tuple type', name.sourceLocation)
return {
name: nodeFactory.stringConstant({
value: name.value.substring(0, parenthesisIndex),
sourceLocation: name.sourceLocation,
}),
propTypes: signatureType.items,
}
} catch (e) {
if (e instanceof Arc4ParseError) {
// Assumes StringConstant is all on one line
throw new CodeError(`Invalid signature: ${e.message}`, {
sourceLocation: new SourceLocation({
...name.sourceLocation,
column: name.sourceLocation.column + parenthesisIndex + e.index,
}),
})
} else {
throw e
}
}
}
4 changes: 2 additions & 2 deletions src/awst_build/eb/arc4/bool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SourceLocation } from '../../../awst/source-location'
import { wtypes } from '../../../awst/wtypes'
import type { PType } from '../../ptypes'
import { boolPType } from '../../ptypes'
import { ARC4BoolClass, ARC4BooleanType, type ARC4EncodedType } from '../../ptypes/arc4-types'
import { ARC4BoolClass, arc4BooleanType, type ARC4EncodedType } from '../../ptypes/arc4-types'
import type { InstanceBuilder, NodeBuilder } from '../index'
import { ClassBuilder } from '../index'
import { parseFunctionArgs } from '../util/arg-parsing'
Expand Down Expand Up @@ -55,6 +55,6 @@ export class BoolClassBuilder extends ClassBuilder {

export class BoolExpressionBuilder extends Arc4EncodedBaseExpressionBuilder<ARC4EncodedType> {
constructor(expression: Expression) {
super(expression, ARC4BooleanType)
super(expression, arc4BooleanType)
}
}
4 changes: 2 additions & 2 deletions src/awst_build/eb/arc4/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { wtypes } from '../../../awst/wtypes'
import type { PType } from '../../ptypes'
import { stringPType } from '../../ptypes'
import type { ARC4EncodedType } from '../../ptypes/arc4-types'
import { ARC4StrClass, ARC4StringType } from '../../ptypes/arc4-types'
import { ARC4StrClass, arc4StringType } from '../../ptypes/arc4-types'
import type { InstanceBuilder, NodeBuilder } from '../index'
import { ClassBuilder } from '../index'
import { parseFunctionArgs } from '../util/arg-parsing'
Expand Down Expand Up @@ -59,6 +59,6 @@ export class StrClassBuilder extends ClassBuilder {

export class StrExpressionBuilder extends Arc4EncodedBaseExpressionBuilder<ARC4EncodedType> {
constructor(expression: Expression) {
super(expression, ARC4StringType)
super(expression, arc4StringType)
}
}
4 changes: 2 additions & 2 deletions src/awst_build/ptypes/arc4-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ export const ARC4StrClass = new LibClassType({
module: Constants.arc4EncodedTypesModuleName,
})

export const ARC4BooleanType = new ARC4InstanceType({
export const arc4BooleanType = new ARC4InstanceType({
name: 'Bool',
wtype: wtypes.arc4BooleanWType,
nativeType: boolPType,
})

export const ARC4StringType = new ARC4InstanceType({
export const arc4StringType = new ARC4InstanceType({
name: 'Str',
wtype: wtypes.arc4StringAliasWType,
nativeType: stringPType,
Expand Down
8 changes: 4 additions & 4 deletions src/awst_build/ptypes/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ import {
AddressClass,
arc4AddressAlias,
ARC4BoolClass,
ARC4BooleanType,
arc4BooleanType,
arc4ByteAlias,
ARC4StrClass,
ARC4StringType,
arc4StringType,
ARC4StructClass,
ARC4StructType,
Arc4TupleClass,
Expand Down Expand Up @@ -351,8 +351,8 @@ export function registerPTypes(typeRegistry: TypeRegistry) {
typeRegistry.register({ ptype: arc4AddressAlias, instanceEb: AddressExpressionBuilder })
typeRegistry.register({ ptype: AddressClass, singletonEb: AddressClassBuilder })
typeRegistry.register({ ptype: ARC4BoolClass, singletonEb: BoolClassBuilder })
typeRegistry.register({ ptype: ARC4BooleanType, instanceEb: BoolExpressionBuilder })
typeRegistry.register({ ptype: ARC4StringType, instanceEb: StrExpressionBuilder })
typeRegistry.register({ ptype: arc4BooleanType, instanceEb: BoolExpressionBuilder })
typeRegistry.register({ ptype: arc4StringType, instanceEb: StrExpressionBuilder })
typeRegistry.register({ ptype: ARC4StrClass, singletonEb: StrClassBuilder })
typeRegistry.register({ ptype: Arc4TupleClass, singletonEb: Arc4TupleClassBuilder })
typeRegistry.registerGeneric({ generic: Arc4TupleGeneric, ptype: ARC4TupleType, instanceEb: Arc4TupleExpressionBuilder })
Expand Down
67 changes: 67 additions & 0 deletions src/util/arc4-signature-parser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, expect, it } from 'vitest'
import { Arc4ParseError, parseArc4Type } from './arc4-signature-parser'
import { invariant } from './index'

describe('arc4 signature parser', () => {
describe('can parse valid types', () => {
it.each([
['byte'],
['byte[]'],
['byte[5]'],
['bool'],
['bool[]'],
['bool[12]'],
['uint8'],
['uint8[]'],
['uint8[12]'],
['uint16'],
['uint16[]'],
['uint16[12]'],
['uint32'],
['uint32[]'],
['uint32[12]'],
['uint64'],
['uint64[]'],
['uint64[12]'],
['uint128'],
['uint128[]'],
['uint128[12]'],
['uint256'],
['uint256[]'],
['uint256[12]'],
['string'],
['string[]'],
['string[12]'],
['byte[][][]'],
['byte[2][2][2]'],
['(bool)'],
['(bool,string,uint8,(uint16,string[]),bool[])[1][]'],
])('%s parses', (arc4TypeString) => {
const match = parseArc4Type(arc4TypeString)

expect(match.wtype.arc4Name).toBe(arc4TypeString)
})
})
describe('errors on invalid types', () => {
it.each([
['float', "Expecting string 'uint', got 'floa...'", 0],
['uint2', 'n must be between 8 and 512, and a multiple of 8', 0],
['(uint8,uint2)', 'n must be between 8 and 512, and a multiple of 8', 7],
['uint8[', "Expecting character ']', but got end of input.", 6],
['uint8]', "Expecting ',', '[', or ')', but got ]", 5],
['uint8,uint8', 'Signature contained more tha one type. Wrap multiple types in parentheses to declare a tuple type', 0],
['(uint8', 'Tuple with items uint8 has not been closed', 6],
['(uint8,bool))', "Char ')' has no matching opening '('", 13],
])('%s returns error %s', (typeString, errorMessage, errorIndex) => {
try {
parseArc4Type(typeString)
} catch (e) {
invariant(e instanceof Arc4ParseError, 'e must be instance of Arc4ParseError')
expect(e.message).toBe(errorMessage)
expect(e.index).toBe(errorIndex)
return
}
expect.fail('Expected error but none was thrown')
})
})
})
Loading

0 comments on commit bcc936b

Please sign in to comment.