Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,366 changes: 868 additions & 498 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"build-examples": "rollup --config examples/rollup.config.ts --configPlugin typescript"
},
"devDependencies": {
"@algorandfoundation/algokit-utils": "^8.0.3",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@eslint/eslintrc": "3.1.0",
Expand Down Expand Up @@ -62,10 +63,8 @@
"tslib": "^2.6.2"
},
"dependencies": {
"@algorandfoundation/algokit-utils": "^6.2.1",
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.22",
"@algorandfoundation/puya-ts": "^1.0.0-alpha.34",
"algosdk": "^2.9.0",
"elliptic": "^6.5.7",
"js-sha256": "^0.11.0",
"js-sha3": "^0.9.3",
Expand Down
10 changes: 7 additions & 3 deletions src/abi-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BaseContract, Contract } from '@algorandfoundation/algorand-typescript'
import { AbiMethodConfig, BareMethodConfig, CreateOptions, OnCompleteActionStr } from '@algorandfoundation/algorand-typescript/arc4'
import { ABIMethod } from 'algosdk'
import { sha512_256 as js_sha512_256 } from 'js-sha512'
import { TypeInfo } from './encoders'
import { getArc4TypeName as getArc4TypeNameForARC4Encoded } from './impl/encoded-types'
import { DeliberateAny } from './typescript-helpers'
Expand Down Expand Up @@ -73,12 +73,16 @@ export const getArc4Signature = (metadata: AbiMetadata): string => {
if (metadata.methodSignature === undefined) {
const argTypes = metadata.argTypes.map((t) => JSON.parse(t) as TypeInfo).map(getArc4TypeName)
const returnType = getArc4TypeName(JSON.parse(metadata.returnType) as TypeInfo)
const method = new ABIMethod({ name: metadata.methodName, args: argTypes.map((t) => ({ type: t })), returns: { type: returnType } })
metadata.methodSignature = method.getSignature()
metadata.methodSignature = `${metadata.methodName}(${argTypes.join(',')})${returnType}`
}
return metadata.methodSignature
}

export const getArc4Selector = (metadata: AbiMetadata): Uint8Array => {
const hash = js_sha512_256.array(getArc4Signature(metadata))
return new Uint8Array(hash.slice(0, 4))
}

const getArc4TypeName = (t: TypeInfo): string => {
const map: Record<string, string | ((t: TypeInfo) => string)> = {
void: 'void',
Expand Down
19 changes: 19 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,22 @@ export const ABI_RETURN_VALUE_LOG_PREFIX = Bytes.fromHex('151F7C75')

export const UINT64_OVERFLOW_UNDERFLOW_MESSAGE = 'Uint64 overflow or underflow'
export const BIGUINT_OVERFLOW_UNDERFLOW_MESSAGE = 'BigUint overflow or underflow'

export const APP_ID_PREFIX = 'appID'
export const HASH_BYTES_LENGTH = 32
export const ALGORAND_ADDRESS_BYTE_LENGTH = 36
export const ALGORAND_CHECKSUM_BYTE_LENGTH = 4
export const ALGORAND_ADDRESS_LENGTH = 58

export const PROGRAM_TAG = 'Program'

export const TRANSACTION_GROUP_MAX_SIZE = 16

export enum OnApplicationComplete {
NoOpOC = 0,
OptInOC = 1,
CloseOutOC = 2,
ClearStateOC = 3,
UpdateApplicationOC = 4,
DeleteApplicationOC = 5,
}
8 changes: 3 additions & 5 deletions src/impl/application.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Account, Application, Bytes, bytes, LocalState, uint64 } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import { Account, Application, bytes, LocalState, uint64 } from '@algorandfoundation/algorand-typescript'
import { BytesMap } from '../collections/custom-key-map'
import { ALWAYS_APPROVE_TEAL_PROGRAM } from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { Mutable } from '../typescript-helpers'
import { asBigInt, asUint64 } from '../util'
import { asUint64, getApplicationAddress } from '../util'
import { Uint64BackedCls } from './base'
import { GlobalStateCls } from './state'

Expand Down Expand Up @@ -76,7 +75,6 @@ export class ApplicationCls extends Uint64BackedCls implements Application {
return this.data.application.creator
}
get address(): Account {
const addr = algosdk.getApplicationAddress(asBigInt(this.id))
return Account(Bytes.fromBase32(addr))
return getApplicationAddress(this.id)
}
}
11 changes: 5 additions & 6 deletions src/impl/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { arc4, bytes, Bytes, Ecdsa, gtxn, internal, VrfVerify } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import { ec } from 'elliptic'
import { sha256 as js_sha256 } from 'js-sha256'
import { keccak256 as js_keccak256, sha3_256 as js_sha3_256 } from 'js-sha3'
import { sha512_256 as js_sha512_256 } from 'js-sha512'
import nacl from 'tweetnacl'
import { LOGIC_DATA_PREFIX } from '../constants'
import { LOGIC_DATA_PREFIX, PROGRAM_TAG } from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { notImplementedError } from '../errors'
import { asBytes, asBytesCls } from '../util'
import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util'

export const sha256 = (a: internal.primitives.StubBytesCompat): bytes => {
const bytesA = internal.primitives.BytesCls.fromCompat(a)
Expand Down Expand Up @@ -59,10 +58,10 @@ export const ed25519verify = (
txn.onCompletion == arc4.OnCompleteAction[arc4.OnCompleteAction.ClearState] ? txn.clearStateProgram : txn.approvalProgram,
)

const logicSig = new algosdk.LogicSig(programBytes.asUint8Array())
const decodedAddress = algosdk.decodeAddress(logicSig.address())
const logicSig = conactUint8Arrays(asUint8Array(PROGRAM_TAG), programBytes.asUint8Array())
const logicSigAddress = js_sha512_256.array(logicSig)

const addressBytes = Bytes(decodedAddress.publicKey)
const addressBytes = Bytes(logicSigAddress)
const data = LOGIC_DATA_PREFIX.concat(addressBytes).concat(asBytes(a))
return ed25519verifyBare(data, b, c)
}
Expand Down
11 changes: 8 additions & 3 deletions src/impl/encoded-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import {
UintN,
} from '@algorandfoundation/algorand-typescript/arc4'
import { encodingUtil } from '@algorandfoundation/puya-ts'
import { decodeAddress } from 'algosdk'
import assert from 'assert'
import { ABI_RETURN_VALUE_LOG_PREFIX, BITS_IN_BYTE, UINT64_SIZE } from '../constants'
import {
ABI_RETURN_VALUE_LOG_PREFIX,
ALGORAND_ADDRESS_BYTE_LENGTH,
ALGORAND_CHECKSUM_BYTE_LENGTH,
BITS_IN_BYTE,
UINT64_SIZE,
} from '../constants'
import { fromBytes, TypeInfo } from '../encoders'
import { DeliberateAny } from '../typescript-helpers'
import { asBigUint, asBigUintCls, asBytesCls, asUint64, asUint8Array, conactUint8Arrays, uint8ArrayToNumber } from '../util'
Expand Down Expand Up @@ -429,7 +434,7 @@ export class AddressImpl extends Address {
if (value === undefined) {
uint8ArrayValue = new Uint8Array(32)
} else if (typeof value === 'string') {
uint8ArrayValue = decodeAddress(value).publicKey
uint8ArrayValue = encodingUtil.base32ToUint8Array(value).slice(0, ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH)
} else if (internal.primitives.isBytes(value)) {
uint8ArrayValue = internal.primitives.getUint8Array(value)
} else {
Expand Down
9 changes: 3 additions & 6 deletions src/impl/global.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Account, Application, Bytes, bytes, internal, op, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import {
DEFAULT_ACCOUNT_MIN_BALANCE,
DEFAULT_ASSET_CREATE_MIN_BALANCE,
Expand All @@ -10,7 +9,7 @@ import {
ZERO_ADDRESS,
} from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { asBigInt, getObjectReference } from '../util'
import { getApplicationAddress, getObjectReference } from '../util'

export class GlobalData {
minTxnFee: uint64
Expand Down Expand Up @@ -128,8 +127,7 @@ export const Global: internal.opTypes.GlobalType = {
* Address that the current application controls. Application mode only.
*/
get currentApplicationAddress(): Account {
const appAddress = algosdk.getApplicationAddress(asBigInt(this.currentApplicationId.id))
return Account(Bytes.fromBase32(appAddress))
return this.currentApplicationId.address
},

/**
Expand Down Expand Up @@ -163,8 +161,7 @@ export const Global: internal.opTypes.GlobalType = {
* The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only.
*/
get callerApplicationAddress(): Account {
const appAddress = algosdk.getApplicationAddress(asBigInt(this.callerApplicationId))
return Account(Bytes.fromBase32(appAddress))
return getApplicationAddress(this.callerApplicationId)
},

/**
Expand Down
5 changes: 2 additions & 3 deletions src/subcontexts/contract-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import {
internal,
LocalState,
} from '@algorandfoundation/algorand-typescript'
import { ABIMethod } from 'algosdk'
import {
AbiMetadata,
copyAbiMetadatas,
getArc4Signature,
getArc4Selector,
getContractAbiMetadata,
getContractMethodAbiMetadata,
isContractProxy,
Expand Down Expand Up @@ -144,7 +143,7 @@ export class ContractContext {
...args: TParams
): Transaction[] {
const app = lazyContext.ledger.getApplicationForContract(contract)
const methodSelector = abiMetadata ? ABIMethod.fromSignature(getArc4Signature(abiMetadata)).getSelector() : new Uint8Array()
const methodSelector = abiMetadata ? getArc4Selector(abiMetadata) : new Uint8Array()
const { transactions, ...appCallArgs } = extractArraysFromArgs(app, methodSelector, args)
const appTxn = lazyContext.any.txn.applicationCall({
appId: app,
Expand Down
12 changes: 5 additions & 7 deletions src/subcontexts/transaction-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { bytes, Contract, internal, TransactionType, uint64 } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import { AbiMetadata, getContractMethodAbiMetadata } from '../abi-metadata'
import { TRANSACTION_GROUP_MAX_SIZE } from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { DecodedLogs, decodeLogs, LogDecoding } from '../decode-logs'
import { testInvariant } from '../errors'
Expand Down Expand Up @@ -180,10 +180,8 @@ export class TransactionGroup {

constructor(transactions: Transaction[], activeTransactionIndex?: number) {
this.latestTimestamp = Date.now()
if (transactions.length > algosdk.AtomicTransactionComposer.MAX_GROUP_SIZE) {
internal.errors.internalError(
`Transaction group can have at most ${algosdk.AtomicTransactionComposer.MAX_GROUP_SIZE} transactions, as per AVM limits.`,
)
if (transactions.length > TRANSACTION_GROUP_MAX_SIZE) {
internal.errors.internalError(`Transaction group can have at most ${TRANSACTION_GROUP_MAX_SIZE} transactions, as per AVM limits.`)
}
transactions.forEach((txn, index) => Object.assign(txn, { groupIndex: asUint64(index) }))
this.activeTransactionIndex = activeTransactionIndex === undefined ? transactions.length - 1 : activeTransactionIndex
Expand Down Expand Up @@ -245,8 +243,8 @@ export class TransactionGroup {
if (!this.constructingItxnGroup.length) {
internal.errors.internalError('itxn submit without itxn begin')
}
if (this.constructingItxnGroup.length > algosdk.AtomicTransactionComposer.MAX_GROUP_SIZE) {
internal.errors.internalError('Cannot submit more than 16 inner transactions at once')
if (this.constructingItxnGroup.length > TRANSACTION_GROUP_MAX_SIZE) {
internal.errors.internalError(`Cannot submit more than ${TRANSACTION_GROUP_MAX_SIZE} inner transactions at once`)
}
const itxns = this.constructingItxnGroup.map((t) => createInnerTxn(t))
itxns.forEach((itxn, index) => Object.assign(itxn, { groupIndex: asUint64(index) }))
Expand Down
6 changes: 3 additions & 3 deletions src/test-execution-context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Account, Application, Asset, Bytes, bytes, internal, uint64 } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import { Account, Application, Asset, bytes, internal, uint64 } from '@algorandfoundation/algorand-typescript'
import { captureMethodConfig } from './abi-metadata'
import { DecodedLogs, LogDecoding } from './decode-logs'
import * as ops from './impl'
Expand All @@ -19,6 +18,7 @@ import { Box, BoxMap, BoxRef, GlobalState, LocalState } from './impl/state'
import { ContractContext } from './subcontexts/contract-context'
import { LedgerContext } from './subcontexts/ledger-context'
import { TransactionContext } from './subcontexts/transaction-context'
import { getRandomBytes } from './util'
import { ValueGenerator } from './value-generators'

export class TestExecutionContext implements internal.ExecutionContext {
Expand All @@ -34,7 +34,7 @@ export class TestExecutionContext implements internal.ExecutionContext {
this.#ledgerContext = new LedgerContext()
this.#txnContext = new TransactionContext()
this.#valueGenerator = new ValueGenerator()
this.#defaultSender = Account(defaultSenderAddress ?? Bytes.fromBase32(algosdk.generateAccount().addr))
this.#defaultSender = Account(defaultSenderAddress ?? getRandomBytes(32).asAlgoTs())
}

account(address?: bytes): Account {
Expand Down
39 changes: 37 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { Bytes, bytes, internal } from '@algorandfoundation/algorand-typescript'
import { Account, Bytes, bytes, internal } from '@algorandfoundation/algorand-typescript'
import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4'
import { encodingUtil } from '@algorandfoundation/puya-ts'
import { randomBytes } from 'crypto'
import { BITS_IN_BYTE, MAX_BYTES_SIZE, MAX_UINT512, MAX_UINT8, UINT512_SIZE } from './constants'
import { sha512_256 as js_sha512_256 } from 'js-sha512'
import {
ALGORAND_ADDRESS_BYTE_LENGTH,
ALGORAND_ADDRESS_LENGTH,
ALGORAND_CHECKSUM_BYTE_LENGTH,
APP_ID_PREFIX,
BITS_IN_BYTE,
HASH_BYTES_LENGTH,
MAX_BYTES_SIZE,
MAX_UINT512,
MAX_UINT8,
UINT512_SIZE,
} from './constants'
import { BytesBackedCls, Uint64BackedCls } from './impl/base'
import { DeliberateAny } from './typescript-helpers'

Expand Down Expand Up @@ -192,3 +205,25 @@ export const conactUint8Arrays = (...values: Uint8Array[]): Uint8Array => {
export const uint8ArrayToNumber = (value: Uint8Array): number => {
return value.reduce((acc, x) => acc * 256 + x, 0)
}

export const checksumFromPublicKey = (pk: Uint8Array): Uint8Array => {
return Uint8Array.from(js_sha512_256.array(pk).slice(HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, HASH_BYTES_LENGTH))
}

export const getApplicationAddress = (appId: internal.primitives.StubUint64Compat): Account => {
const toBeSigned = conactUint8Arrays(asUint8Array(APP_ID_PREFIX), encodingUtil.bigIntToUint8Array(asBigInt(appId), 8))
const appIdHash = js_sha512_256.array(toBeSigned)
const publicKey = Uint8Array.from(appIdHash)
const address = encodeAddress(publicKey)
return Account(Bytes.fromBase32(address))
}

export const encodeAddress = (address: Uint8Array): string => {
const checksum = checksumFromPublicKey(address)
return encodingUtil.uint8ArrayToBase32(conactUint8Arrays(address, checksum)).slice(0, ALGORAND_ADDRESS_LENGTH)
}

export const decodePublicKey = (address: string): Uint8Array => {
const decoded = encodingUtil.base32ToUint8Array(address)
return decoded.slice(0, ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH)
}
5 changes: 2 additions & 3 deletions src/value-generators/avm.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Account, Application, Asset, bytes, Bytes, internal, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
import algosdk from 'algosdk'
import { randomBytes } from 'crypto'
import { MAX_BYTES_SIZE, MAX_UINT64, ZERO_ADDRESS } from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { AccountData } from '../impl/account'
import { ApplicationCls, ApplicationData } from '../impl/application'
import { AssetCls, AssetData } from '../impl/asset'
import { asBigInt, asUint64Cls, getRandomBigInt } from '../util'
import { asBigInt, asUint64Cls, getRandomBigInt, getRandomBytes } from '../util'

type AccountContextData = Partial<AccountData['account']> & {
address?: Account
Expand Down Expand Up @@ -37,7 +36,7 @@ export class AvmValueGenerator {
}

account(input?: AccountContextData): Account {
const account = input?.address ?? Account(Bytes.fromBase32(algosdk.generateAccount().addr))
const account = input?.address ?? Account(getRandomBytes(32).asAlgoTs())

if (input?.address && lazyContext.ledger.accountDataMap.has(account)) {
internal.errors.internalError(
Expand Down
15 changes: 8 additions & 7 deletions tests/arc4/address.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56'
import { Account, Bytes } from '@algorandfoundation/algorand-typescript'
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
import { Address, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4'
import { ABIType, encodeAddress } from 'algosdk'
import { afterEach, describe, expect, test } from 'vitest'
import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants'
import { encodeAddress } from '../../src/util'
import { asUint8Array } from '../util'

const abiAddressType = ABIType.from('address')
const abiTypeString = 'address'
const testData = [
Bytes.fromHex('00'.repeat(32)),
Bytes.fromHex('01'.repeat(32)),
Expand All @@ -21,19 +22,19 @@ describe('arc4.Address', async () => {
})

test.each(testData)('create address from bytes', async (value) => {
const sdkResult = abiAddressType.encode(asUint8Array(value))
const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {})
const result = new Address(value)
expect(result.bytes).toEqual(sdkResult)
})
test.each(testData)('create address from str', async (value) => {
const stringValue = encodeAddress(asUint8Array(value))
const sdkResult = abiAddressType.encode(stringValue)
const sdkResult = getABIEncodedValue(stringValue, abiTypeString, {})
const result = new Address(stringValue)
expect(result.bytes).toEqual(sdkResult)
})
test.each(testData)('create address from Account', async (value) => {
const accountValue = Account(value)
const sdkResult = abiAddressType.encode(asUint8Array(accountValue.bytes))
const sdkResult = getABIEncodedValue(asUint8Array(accountValue.bytes), abiTypeString, {})
const result = new Address(accountValue)
expect(result.bytes).toEqual(sdkResult)
})
Expand Down Expand Up @@ -69,13 +70,13 @@ describe('arc4.Address', async () => {
})

test.each(testData)('fromBytes method', async (value) => {
const sdkResult = abiAddressType.encode(asUint8Array(value))
const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {})
const result = interpretAsArc4<Address>(value)
expect(result.bytes).toEqual(sdkResult)
})

test.each(testData)('fromLog method', async (value) => {
const sdkResult = abiAddressType.encode(asUint8Array(value))
const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {})
const paddedValue = Bytes([...asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX), ...asUint8Array(value)])
const result = interpretAsArc4<Address>(paddedValue, 'log')
expect(result.bytes).toEqual(sdkResult)
Expand Down
Loading
Loading