Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4a190fe
Lib: Introduce Result and QueryResponse types
ncomerci Dec 4, 2025
c9aa70f
feat: Enhance price retrieval with Result type and error handling
ncomerci Dec 4, 2025
1dd2719
refactor: use QueryResponse class
ncomerci Dec 4, 2025
5d32b5c
fix: Implement workaround for JSON boolean parsing issue
ncomerci Dec 4, 2025
9f3e14a
fix: tests
ncomerci Dec 4, 2025
1046aa1
feat: implement query response for relevant tokens
ncomerci Dec 4, 2025
a793962
feat: implement query result for evm call
ncomerci Dec 9, 2025
ba3dde2
fix: FunctionHandler to return Result type for EVM calls
ncomerci Dec 9, 2025
441e7ef
feat: implement query result for subgraph queries
ncomerci Dec 9, 2025
1ab1de8
feat: implement query result for svm query
ncomerci Dec 9, 2025
6c2e61f
refactor: query responses
ncomerci Dec 9, 2025
5812fac
fix: lib test
ncomerci Dec 9, 2025
7d19e25
fix: cli tests
ncomerci Dec 9, 2025
406b3a8
fix: handle errors in SPLToken metadata queries with logging
ncomerci Dec 9, 2025
9ef43ba
fix: update default values for mock functions in RunnerMock
ncomerci Dec 10, 2025
d1405b4
fix: update mock responses
ncomerci Dec 10, 2025
828e932
refactor: update TokenAmount and USD classes to return Result type
ncomerci Dec 11, 2025
9b637f4
chore: requested changes
ncomerci Dec 11, 2025
5ebf027
feat: introduce Void type for handling void return values in Result
ncomerci Dec 16, 2025
25f4ecc
refactor: FunctionHandler
ncomerci Dec 16, 2025
b9252b8
fix: rebase
ncomerci Dec 18, 2025
33b5b4e
chore: add changeset
ncomerci Dec 19, 2025
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
7 changes: 7 additions & 0 deletions .changeset/spicy-flowers-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@mimicprotocol/test-ts": patch
"@mimicprotocol/lib-ts": patch
"@mimicprotocol/cli": patch
---

Handle oracle query errors
34 changes: 22 additions & 12 deletions packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,21 @@ export default class FunctionHandler {
isPayable ? `${methodParams.length > 0 ? ', ' : ''}value: ${LibTypes.BigInt}` : ''
)

lines.push(` ${methodName}(${fullMethodParams}): ${returnType} {`)
lines.push(`${methodName}(${fullMethodParams}): ${returnType} {`)

lines.push(
` const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})`
`const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})`
)

importManager.addType(LibTypes.Bytes)
importManager.addType('EvmCallBuilder')
if (isPayable) importManager.addType(LibTypes.BigInt)

lines.push(
` return EvmCallBuilder.forChain(this._chainId).addCall(this._address, encodedData${isPayable ? ', value' : ''})`
`return EvmCallBuilder.forChain(this._chainId).addCall(this._address, encodedData${isPayable ? ', value' : ''})`
)

lines.push(` }`)
lines.push(`}`)
lines.push('')
}

Expand All @@ -122,23 +122,33 @@ export default class FunctionHandler {
const methodName = fn.escapedName || fn.name
const capitalizedName = this.getCapitalizedName(fn)

lines.push(` ${methodName}(${methodParams}): ${returnType} {`)
importManager.addType('environment')
importManager.addType('Result')
const isVoid = returnType === 'void'
if (isVoid) importManager.addType(LibTypes.Void)

const resultReturnType = isVoid ? `Result<${LibTypes.Void}, string>` : `Result<${returnType}, string>`
lines.push(`${methodName}(${methodParams}): ${resultReturnType} {`)

lines.push(
` const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})`
`const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})`
)

importManager.addType('environment')
const contractCallLine = `environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)`

if (returnType === 'void') {
lines.push(` ${contractCallLine}`)
lines.push(`const response = ${contractCallLine}`)
lines.push(
`if (response.isError) return Result.err<${isVoid ? LibTypes.Void : returnType}, string>(response.error)`
)

if (isVoid) {
lines.push(`return Result.ok<${LibTypes.Void}, string>(new ${LibTypes.Void}())`)
} else {
lines.push(` const response = ${contractCallLine}`)
lines.push(` return ${contractName}Utils.decode${capitalizedName}(response)`)
lines.push(`const decoded = ${contractName}Utils.decode${capitalizedName}(response.value)`)
lines.push(`return Result.ok<${returnType}, string>(decoded)`)
}

lines.push(` }`)
lines.push(`}`)
lines.push('')
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/lib/AbisInterfaceGenerator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type ImportedTypes =
| 'EvmDecodeParam'
| 'JSON'
| 'EvmCallBuilder'
| 'Result'

export type MapBaseTypeCallback = (param: AbiParameter) => string
export type TupleDefinition = {
className: string
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum LibTypes {
Bytes = 'Bytes',
ChainId = 'ChainId',
TokenAmount = 'TokenAmount',
Void = 'Void',
}

export enum AssemblyPrimitiveTypes {
Expand Down
27 changes: 17 additions & 10 deletions packages/cli/tests/AbisInterfaceGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('AbisInterfaceGenerator', () => {

const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME)

expect(result).to.contain(`getData(): ${LibTypes.BigInt} {`)
expect(result).to.contain(`getData(): Result<${LibTypes.BigInt}, string> {`)
expect(result).to.contain(`getData_1(id: ${LibTypes.BigInt}): EvmCallBuilder {`)
expect(result).to.contain(
`getData_2(id: ${LibTypes.BigInt}, flag: ${AssemblyPrimitiveTypes.bool}, value: ${LibTypes.BigInt}): EvmCallBuilder {`
Expand All @@ -120,10 +120,10 @@ describe('AbisInterfaceGenerator', () => {

const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME)

expect(result).to.contain(`getBalance(): ${LibTypes.BigInt} {`)
expect(result).to.contain(`getBalance(): Result<${LibTypes.BigInt}, string> {`)
expect(result).to.contain(`transfer(to: ${LibTypes.Address}): EvmCallBuilder {`)
expect(result).to.contain(`getBalance_1(owner: ${LibTypes.Address}): ${LibTypes.BigInt} {`)
expect(result).to.contain(`getName(): ${AssemblyPrimitiveTypes.string} {`)
expect(result).to.contain(`getBalance_1(owner: ${LibTypes.Address}): Result<${LibTypes.BigInt}, string> {`)
expect(result).to.contain(`getName(): Result<${AssemblyPrimitiveTypes.string}, string> {`)
expect(result).to.contain(
`transfer_1(to: ${LibTypes.Address}, amount: ${LibTypes.BigInt}, value: ${LibTypes.BigInt}): EvmCallBuilder {`
)
Expand All @@ -137,7 +137,7 @@ describe('AbisInterfaceGenerator', () => {

const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME)

expect(result).to.contain(`constructor_(): ${LibTypes.BigInt} {`)
expect(result).to.contain(`constructor_(): Result<${LibTypes.BigInt}, string> {`)
expect(result).to.contain(`constructor_1(value: ${LibTypes.BigInt}): EvmCallBuilder {`)
})
})
Expand Down Expand Up @@ -239,7 +239,9 @@ describe('AbisInterfaceGenerator', () => {
expect(result).to.contain(
`const response = environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)`
)
expect(result).to.contain(`return ${CONTRACT_NAME}Utils.decodeGetBalance(response)`)
expect(result).to.contain(`if (response.isError) return Result.err<${LibTypes.BigInt}, string>(response.error)`)
expect(result).to.contain(`const decoded = ${CONTRACT_NAME}Utils.decodeGetBalance(response.value)`)
expect(result).to.contain(`return Result.ok<${LibTypes.BigInt}, string>(decoded)`)
expect(result).to.contain(`export class ${CONTRACT_NAME}Utils {`)
expect(result).to.contain(`static encodeGetBalance(owner: Address): Bytes {`)
expect(result).to.contain(`static decodeGetBalance(encodedResponse: string): BigInt {`)
Expand Down Expand Up @@ -311,12 +313,14 @@ describe('AbisInterfaceGenerator', () => {

const selector = getFunctionSelector(abi[0])

expect(result).to.contain(`${functionName}(): void {`)
expect(result).to.contain(`${functionName}(): Result<Void, string> {`)
expect(result).to.contain(`static encodeNoReturn(): Bytes {`)
expect(result).to.contain(`return ${LibTypes.Bytes}.fromHexString('${selector}')`)
expect(result).to.contain(
`environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)`
)
expect(result).to.contain(`if (response.isError) return Result.err<Void, string>(response.error)`)
expect(result).to.contain(`return Result.ok<Void, string>(new Void())`)
expect(result).not.to.contain(`_decodeNoReturnResponse`)
})
})
Expand All @@ -341,6 +345,7 @@ describe('AbisInterfaceGenerator', () => {
expect(importMatch).to.contain('EvmDecodeParam')
expect(importMatch).to.contain('evm')
expect(importMatch).to.contain('environment')
expect(importMatch).to.contain('Result')
})
})

Expand Down Expand Up @@ -863,7 +868,7 @@ describe('AbisInterfaceGenerator', () => {

expect(result).to.contain('static encodeGetBalance(owner: Address): Bytes {')
expect(result).to.contain('static decodeGetBalance(encodedResponse: string): BigInt {')
expect(result).to.contain('getBalance(owner: Address): BigInt {')
expect(result).to.contain('getBalance(owner: Address): Result<BigInt, string> {')
})

it('should generate encoded data method for write functions', () => {
Expand All @@ -887,7 +892,7 @@ describe('AbisInterfaceGenerator', () => {
const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME)

expect(result).to.contain('static encodeValidate(data: Bytes): Bytes {')
expect(result).to.contain('validate(data: Bytes): void {')
expect(result).to.contain('validate(data: Bytes): Result<Void, string> {')
expect(result).not.to.contain('_decodeValidateResponse')
})

Expand All @@ -901,7 +906,9 @@ describe('AbisInterfaceGenerator', () => {

// Read function should call both helpers
expect(result).to.contain(`const encodedData = ${CONTRACT_NAME}Utils.encodeGetValue()`)
expect(result).to.contain(`return ${CONTRACT_NAME}Utils.decodeGetValue(response)`)
expect(result).to.contain(`if (response.isError) return Result.err<${LibTypes.BigInt}, string>(response.error)`)
expect(result).to.contain(`const decoded = ${CONTRACT_NAME}Utils.decodeGetValue(response.value)`)
expect(result).to.contain(`return Result.ok<${LibTypes.BigInt}, string>(decoded)`)

// Write function should call encoded data helper
expect(result).to.contain(`const encodedData = ${CONTRACT_NAME}Utils.encodeSetValue(value)`)
Expand Down
8 changes: 4 additions & 4 deletions packages/integration/tests/005-get-price-query/mock.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"_tokenPriceQuery": {
"log": true,
"paramResponse": {
"{\"address\":\"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599\",\"chainId\":1}": "[\"90000000000000000000000\"]",
"{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1}": "[\"1000000000000000000\"]",
"{\"address\":\"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"chainId\":1}": "[\"2000000000000000000000\"]",
"{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1,\"timestamp\":1744818017000}": "[\"1000000000000000000\"]"
"{\"address\":\"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"90000000000000000000000\"],\"error\":\"\"}",
"{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"1000000000000000000000\"],\"error\":\"\"}",
"{\"address\":\"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"2000000000000000000000\"],\"error\":\"\"}",
"{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1,\"timestamp\":1744818017000}": "{\"success\":\"true\",\"data\":[\"1000000000000000000000\"],\"error\":\"\"}"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/integration/tests/005-get-price-query/src/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function main(): void {

// Convert X amount of USDC to USD
const usdcAmount = TokenAmount.fromStringDecimal(Ethereum.USDC, '1000.2')
usdcAmount.toUsd().toString()
usdcAmount.toUsd()

// Convert USDC to ETH
usdcAmount.toTokenAmount(Ethereum.ETH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"environment": {
"_relevantTokensQuery": {
"log": true,
"default": "[]"
"default": "{\"success\":\"true\",\"data\":[],\"error\":\"\"}"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"environment": {
"_evmCallQuery": {
"log": true,
"default": "0x047be3bb46f9416732fe39a05134f20235c19334"
"default": "{\"success\":\"true\",\"data\":\"0x047be3bb46f9416732fe39a05134f20235c19334\",\"error\":\"\"}"
}
},
"evm": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"environment": {
"_evmCallQuery": {
"log": true,
"default": "0x047be3bb46f9416732fe39a05134f20235c19334"
"default": "{\"success\":\"true\",\"data\":\"0x047be3bb46f9416732fe39a05134f20235c19334\",\"error\":\"\"}"
},
"_getContext": "{ \"timestamp\": 1438223173000, \"consensusThreshold\": 1, \"user\": \"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0\", \"settlers\": [{\"address\": \"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d\", \"chainId\": 10}], \"configSig\": \"682ec8210b1ce912da4d2952\"}",
"_relevantTokensQuery": "[{\"timestamp\":1438223173000,\"balances\":[{\"token\":{\"address\":\"0x625e7708f30ca75bfd92586e17077590c60eb4cd\",\"chainId\":10},\"balance\":\"100000000\"}]}]",
"_relevantTokensQuery": "{\"success\":\"true\",\"data\":[{\"timestamp\":1438223173000,\"balances\":[{\"token\":{\"address\":\"0x625e7708f30ca75bfd92586e17077590c60eb4cd\",\"chainId\":10},\"balance\":\"100000000\"}]}],\"error\":\"\"}",
"_call": {
"log": true,
"default": ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ export default function main(): void {

const context = environment.getContext()

const userTokens = environment.relevantTokensQuery(context.user, [chainId], USD.zero(), [aUSDC], ListType.AllowList)
const userTokensResult = environment.relevantTokensQuery(
context.user,
[chainId],
USD.zero(),
[aUSDC],
ListType.AllowList
)
if (userTokensResult.isError) throw new Error(userTokensResult.error)

const userTokens = userTokensResult.value

const feeUsdt = TokenAmount.fromStringDecimal(USDT, inputs.usdFeeAmount)

Expand Down
36 changes: 30 additions & 6 deletions packages/lib-ts/as-pect.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,33 @@ export default {
const timestamp = params.timestamp
const key = `_tokenPriceQuery:${address}:${chainId}${timestamp ? `:${timestamp}` : ''}`

if (store.has(key)) return exports.__newString(`["${store.get(key)}"]`)
throw new Error(`Price not found for key: ${key}`)
if (store.has(key)) {
const response = JSON.stringify(Result.ok([store.get(key).toString()]))
return exports.__newString(response)
}
return exports.__newString(JSON.stringify(Result.err(`Price not found for key: ${key}`)))
},
_evmCallQuery: (paramsPtr) => {
const paramsStr = exports.__getString(paramsPtr)
const params = JSON.parse(paramsStr)
const key = `_evmCallQuery:${params.to.toLowerCase()}:${params.chainId}:${params.data.toLowerCase()}`

if (store.has(key)) return exports.__newString(store.get(key))
throw new Error(`Contract call result not found for key: ${key}`)
if (store.has(key)) return exports.__newString(JSON.stringify(Result.ok(store.get(key))))
return exports.__newString(JSON.stringify(Result.err(`Contract call result not found for key: ${key}`)))
},
_svmAccountsInfoQuery: (paramsPtr) => {
const paramsStr = exports.__getString(paramsPtr)
const params = JSON.parse(paramsStr)
const publicKeys = params.publicKeys.join(',')
const key = `_svmAccountsInfoQuery:${publicKeys}`

if (store.has(key)) return exports.__newString(store.get(key))
throw new Error(`Get accounts info result not found for key: ${key}`)
if (store.has(key)) {
const storedValue = store.get(key)
const accountsInfoData = typeof storedValue === 'string' ? JSON.parse(storedValue) : storedValue
return exports.__newString(JSON.stringify(Result.ok(accountsInfoData)))
}

return exports.__newString(JSON.stringify(Result.err(`Get accounts info result not found for key: ${key}`)))
},
_getContext: () => {
const key = `_getContext`
Expand Down Expand Up @@ -168,3 +176,19 @@ export default {
*/
outputBinary: false,
}

class Result {
constructor(success, data, error) {
this.success = success
this.data = data
this.error = error
}

static ok(data) {
return new Result(true, data, '')
}

static err(error) {
return new Result(false, '', error)
}
}
Loading