diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dc6b904e..1165f75b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -369,6 +369,12 @@ importers: '@graphprotocol/graph-cli': specifier: ^0.97.0 version: 0.97.1(typescript@5.8.3) + assemblyscript: + specifier: 0.19.23 + version: 0.19.23 + matchstick-as: + specifier: ^0.6.0 + version: 0.6.0 packages: @@ -484,10 +490,10 @@ packages: '@babel/helpers': 7.27.6 '@babel/parser': 7.27.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.27.4(supports-color@5.5.0) '@babel/types': 7.27.6 convert-source-map: 2.0.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -571,7 +577,7 @@ packages: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.27.1(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -601,7 +607,7 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.27.1(supports-color@5.5.0) '@babel/types': 7.27.1 transitivePeerDependencies: - supports-color @@ -614,15 +620,6 @@ packages: '@babel/types': 7.24.5 dev: true - /@babel/helper-module-imports@7.27.1: - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 - transitivePeerDependencies: - - supports-color - /@babel/helper-module-imports@7.27.1(supports-color@5.5.0): resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -631,7 +628,6 @@ packages: '@babel/types': 7.27.1 transitivePeerDependencies: - supports-color - dev: false /@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} @@ -654,9 +650,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.27.4 - '@babel/helper-module-imports': 7.27.1 + '@babel/helper-module-imports': 7.27.1(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.27.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -680,7 +676,7 @@ packages: '@babel/core': 7.27.4 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.27.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -696,7 +692,7 @@ packages: resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.27.1(supports-color@5.5.0) '@babel/types': 7.27.1 transitivePeerDependencies: - supports-color @@ -921,20 +917,6 @@ packages: - supports-color dev: true - /@babel/traverse@7.27.1: - resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.1 - '@babel/parser': 7.27.1 - '@babel/template': 7.27.1 - '@babel/types': 7.27.1 - debug: 4.4.1(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - /@babel/traverse@7.27.1(supports-color@5.5.0): resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==} engines: {node: '>=6.9.0'} @@ -948,21 +930,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: false - - /@babel/traverse@7.27.4: - resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.5 - '@babel/template': 7.27.2 - '@babel/types': 7.27.6 - debug: 4.4.1(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color /@babel/traverse@7.27.4(supports-color@5.5.0): resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} @@ -977,7 +944,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: false /@babel/types@7.24.5: resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} @@ -1995,7 +1961,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2025,7 +1991,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -2698,7 +2664,7 @@ packages: '@whatwg-node/fetch': 0.10.8 assemblyscript: 0.19.23 chokidar: 4.0.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) docker-compose: 1.2.0 fs-extra: 11.3.0 glob: 11.0.2 @@ -3192,7 +3158,7 @@ packages: '@babel/core': 7.27.4 '@babel/parser': 7.27.5 '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.27.4) - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.27.4(supports-color@5.5.0) '@babel/types': 7.27.6 '@graphql-tools/utils': 10.8.6(graphql@16.11.0) graphql: 16.11.0 @@ -3271,7 +3237,7 @@ packages: '@types/js-yaml': 4.0.9 '@whatwg-node/fetch': 0.10.8 chalk: 4.1.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) dotenv: 16.5.0 graphql: 16.11.0 graphql-request: 6.1.0(graphql@16.11.0) @@ -4104,7 +4070,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.1.0 date-fns: 2.30.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eciesjs: 0.4.15 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -4132,7 +4098,7 @@ packages: '@paulmillr/qr': 0.2.1 bowser: 2.11.0 cross-fetch: 4.1.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eciesjs: 0.4.15 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -4161,7 +4127,7 @@ packages: dependencies: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) semver: 7.7.2 superstruct: 1.0.4 transitivePeerDependencies: @@ -4177,7 +4143,7 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) pony-cause: 2.1.11 semver: 7.7.2 uuid: 9.0.1 @@ -4194,7 +4160,7 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) pony-cause: 2.1.11 semver: 7.7.2 uuid: 9.0.1 @@ -4853,7 +4819,7 @@ packages: '@ethersproject/address': 5.8.0 cbor: 8.1.0 chalk: 2.4.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fs-extra: 7.0.1 hardhat: 2.22.3(ts-node@10.9.2)(typescript@5.5.4) lodash: 4.17.21 @@ -4962,7 +4928,7 @@ packages: dependencies: '@oclif/core': 4.3.0 ansis: 3.17.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) ejs: 3.1.10 transitivePeerDependencies: - supports-color @@ -4986,7 +4952,7 @@ packages: dependencies: '@oclif/core': 4.3.0 ansis: 3.17.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) http-call: 5.3.0 lodash: 4.17.21 registry-auth-token: 5.1.0 @@ -5555,7 +5521,7 @@ packages: /@pm2/pm2-version-check@1.0.4: resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -6782,7 +6748,7 @@ packages: big.js: 6.2.1 bn.js: 5.2.1 cbor: 5.2.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) lodash: 4.17.21 semver: 7.7.2 utf8: 3.0.0 @@ -6806,7 +6772,7 @@ packages: deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. dependencies: ajv: 6.12.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -6823,7 +6789,7 @@ packages: '@truffle/error': 0.2.2 '@truffle/interface-adapter': 0.5.37 bignumber.js: 7.2.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) ethers: 4.0.49 web3: 1.10.0 web3-core-helpers: 1.10.0 @@ -6846,7 +6812,7 @@ packages: '@trufflesuite/chromafi': 3.0.0 bn.js: 5.2.1 chalk: 2.4.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) highlightjs-solidity: 2.0.6 transitivePeerDependencies: - supports-color @@ -7285,7 +7251,7 @@ packages: '@typescript-eslint/types': 8.34.1 '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.34.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eslint: 9.29.0 typescript: 5.8.3 transitivePeerDependencies: @@ -7300,7 +7266,7 @@ packages: dependencies: '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) '@typescript-eslint/types': 8.34.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -7332,7 +7298,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) '@typescript-eslint/utils': 8.34.1(eslint@9.29.0)(typescript@5.8.3) - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eslint: 9.29.0 ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 @@ -7355,7 +7321,7 @@ packages: '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) '@typescript-eslint/types': 8.34.1 '@typescript-eslint/visitor-keys': 8.34.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -7616,7 +7582,7 @@ packages: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 ast-v8-to-istanbul: 0.3.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -8594,7 +8560,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -10663,7 +10629,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 5.5.0 - dev: false /debug@4.4.1(supports-color@8.1.1): resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} @@ -10676,6 +10641,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 + dev: true /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} @@ -11602,7 +11568,7 @@ packages: optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eslint: 9.29.0 eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0) get-tsconfig: 4.10.1 @@ -11785,7 +11751,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -12635,7 +12601,7 @@ packages: debug: optional: true dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) dev: true /for-each@0.3.3: @@ -12979,7 +12945,7 @@ packages: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fs-extra: 11.3.0 transitivePeerDependencies: - supports-color @@ -13462,7 +13428,7 @@ packages: chalk: 2.4.2 chokidar: 3.6.0 ci-info: 2.0.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -13514,6 +13480,7 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + dev: true /has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -13677,7 +13644,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: content-type: 1.0.5 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) is-retry-allowed: 1.2.0 is-stream: 2.0.1 parse-json: 4.0.0 @@ -13706,7 +13673,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13776,7 +13743,7 @@ packages: requiresBuild: true dependencies: agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -13786,7 +13753,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -14451,7 +14418,7 @@ packages: engines: {node: '>=10'} dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -15362,6 +15329,12 @@ packages: resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} dev: true + /matchstick-as@0.6.0: + resolution: {integrity: sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw==} + dependencies: + wabt: 1.0.24 + dev: true + /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -16462,7 +16435,7 @@ packages: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) get-uri: 6.0.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -16894,7 +16867,7 @@ packages: resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} engines: {node: '>=5'} dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -16905,7 +16878,7 @@ packages: dependencies: amp: 0.3.1 amp-message: 0.1.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) escape-string-regexp: 4.0.0 transitivePeerDependencies: - supports-color @@ -16930,7 +16903,7 @@ packages: requiresBuild: true dependencies: async: 3.2.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) pidusage: 2.0.21 systeminformation: 5.23.5 tx2: 1.0.5 @@ -17304,7 +17277,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -17775,7 +17748,7 @@ packages: resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} engines: {node: '>=6'} dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -18460,7 +18433,7 @@ packages: requiresBuild: true dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -18989,6 +18962,7 @@ packages: engines: {node: '>=10'} dependencies: has-flag: 4.0.0 + dev: true /supports-color@9.4.0: resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} @@ -19555,7 +19529,7 @@ packages: typescript: '>=4.3.0' dependencies: '@types/prettier': 2.7.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fs-extra: 7.0.1 glob: 7.1.7 js-sha3: 0.8.0 @@ -20160,7 +20134,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@24.0.4)(tsx@4.20.3) @@ -20193,7 +20167,7 @@ packages: '@volar/typescript': 2.4.14 '@vue/language-core': 2.2.0(typescript@5.8.3) compare-versions: 6.1.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) kolorist: 1.8.0 local-pkg: 1.1.1 magic-string: 0.30.17 @@ -20364,7 +20338,7 @@ packages: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) expect-type: 1.2.1 jsdom: 26.1.0 magic-string: 0.30.17 @@ -20414,6 +20388,11 @@ packages: dependencies: xml-name-validator: 5.0.0 + /wabt@1.0.24: + resolution: {integrity: sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg==} + hasBin: true + dev: true + /wagmi@2.15.6(@tanstack/react-query@5.81.2)(@types/react@19.1.8)(react@19.1.0)(typescript@5.8.3)(viem@2.31.4): resolution: {integrity: sha512-tR4tm+7eE0UloQe1oi4hUIjIDyjv5ImQlzq/QcvvfJYWF/EquTfGrmht6+nTYGCIeSzeEvbK90KgWyNqa+HD7Q==} peerDependencies: diff --git a/subgraph/.gitignore b/subgraph/.gitignore index bd23e5643..aca23bb4b 100644 --- a/subgraph/.gitignore +++ b/subgraph/.gitignore @@ -5,3 +5,5 @@ /troves.json.bak /brackets.json /brackets.json.bak +/tests/.bin/ +/tests/.latest.json diff --git a/subgraph/networks.json b/subgraph/networks.json index b24d2abfd..a5b13ea94 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -1,30 +1,41 @@ { - "local": { + "celo": { "BoldToken": { - "address": "0x0e18b884ec3095f7c27bbbeb0a266a5674bcaffd" + "address": "0xb38aEf2bF4e34B997330D626EBCd7629De3885C9", + "startBlock": 60668167 }, - "Governance": { - "address": "0x79fdb65c20e75f8961d54f6714d2fcc4c30d1073" + "BoldTokenCHFm": { + "address": "0x4e105fEf015dB26320c077427bd605acEad9262E", + "startBlock": 65390610 + }, + "BoldTokenJPYm": { + "address": "0xd2E65aF47d927d5E84f384AE6BAC4F97c3da65df", + "startBlock": 65390636 } }, - "mainnet": { + "celo-sepolia": { "BoldToken": { - "address": "0x6440f144b7e50d6a8439336510312d2f54beb01d", - "startBlock": 22483043 + "address": "0x3ADFF16949513480E051F451748Ee8B801731c46", + "startBlock": 19187423 + }, + "BoldTokenCHFm": { + "address": "0xc21dc07eC1707d315A7eff31A6556e628E43A3f8", + "startBlock": 23671277 }, - "Governance": { - "address": "0x807def5e7d057df05c796f4bc75c3fe82bd6eee1", - "startBlock": 22496547 + "BoldTokenJPYm": { + "address": "0xeD54259166EbbFe72083FC37eE2a00e922fbA84D", + "startBlock": 23671448 } }, - "sepolia": { + "local": { "BoldToken": { - "address": "0x181dff47198bf3f3ed65877332e8395eb6817c4c", - "startBlock": 8316063 + "address": "0x0e18b884ec3095f7c27bbbeb0a266a5674bcaffd" + }, + "BoldTokenCHFm": { + "address": "0x0000000000000000000000000000000000000000" }, - "Governance": { - "address": "0xf68db4e851d89daf0fb842b01f75d94e0cb44f49", - "startBlock": 8316191 + "BoldTokenJPYm": { + "address": "0x0000000000000000000000000000000000000000" } } } diff --git a/subgraph/package.json b/subgraph/package.json index 58f271daa..e70d53250 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -4,10 +4,14 @@ "license": "MIT", "version": "1.0.0", "scripts": { - "codegen": "pnpm graph codegen" + "codegen": "pnpm graph codegen", + "build": "pnpm graph build", + "test": "pnpm graph test" }, "devDependencies": { - "@graphprotocol/graph-cli": "^0.97.0" + "@graphprotocol/graph-cli": "^0.97.0", + "assemblyscript": "0.19.23", + "matchstick-as": "^0.6.0" }, "dependencies": { "@graphprotocol/graph-ts": "^0.38.0", diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index ce8e82df3..ac1d88e10 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -54,6 +54,52 @@ type Trove @entity(immutable: false) { redemptionCount: Int! redeemedColl: BigInt! redeemedDebt: BigInt! + operations: [TroveOperation!]! @derivedFrom(field: "trove") +} + +# Mirrors ITroveEvents.Operation. Order must match the contract enum so a uint8 +# can be stored as the corresponding string value with no remapping. +enum TroveOperationKind { + openTrove + closeTrove + adjustTrove + adjustTroveInterestRate + applyPendingDebt + liquidate + redeemCollateral + openTroveAndJoinBatch + setInterestBatchManager + removeFromBatch +} + +# Immutable per-operation history row. One row per TroveOperation event. +# IDs combine tx hash + log index so they are stable across reorgs. +type TroveOperation @entity(immutable: true) { + id: Bytes! # tx hash concatenated with logIndex + trove: Trove! + blockNumber: BigInt! + timestamp: BigInt! + transactionHash: Bytes! + logIndex: BigInt! + operation: TroveOperationKind! + initiator: Bytes! # transaction.from + # Signed changes from the operation itself. Excludes accrued/redistributed values. + collateralDelta: BigInt! + debtDelta: BigInt! + # Post-state snapshots, captured after TroveUpdated has been applied. + newCollateral: BigInt! + newDebt: BigInt! + newInterestRate: BigInt! + # Accrued portions from redistribution. Always positive. + collIncreaseFromRedist: BigInt! + debtIncreaseFromRedist: BigInt! + # Upfront borrowing fee added to debt this op (zero outside open/adjust paths). + upfrontFee: BigInt! + # Enriched from same-tx Redemption / Liquidation events when applicable. + redemptionPrice: BigInt + liquidationPrice: BigInt + # Set when the op happened against a trove in a batch. + batch: InterestBatch } type BorrowerInfo @entity(immutable: false) { @@ -74,34 +120,3 @@ type InterestBatch @entity(immutable: false) { updatedAt: BigInt! troves: [Trove!]! @derivedFrom(field: "interestBatch") } - -type GovernanceVotingPower @entity(immutable: false) { - id: ID! # userAddress, e.g. "0x0000000000000000000000000000000000000000", or "total" - allocatedLQTY: BigInt! - allocatedOffset: BigInt! - unallocatedLQTY: BigInt! - unallocatedOffset: BigInt! -} - -type GovernanceAllocationIndex @entity(immutable: false) { - id: ID! # "userAddress:initiativeAddress" or "initiativeAddress" - user: String - initiative: GovernanceInitiative! - latestAllocation: GovernanceAllocation! -} - -type GovernanceAllocation @entity(immutable: false) { - id: ID! # "userAddress:initiativeAddress:epoch" or "initiativeAddress:epoch" - user: String - initiative: GovernanceInitiative! - epoch: BigInt! - voteLQTY: BigInt! - vetoLQTY: BigInt! - voteOffset: BigInt! - vetoOffset: BigInt! -} - -type GovernanceInitiative @entity(immutable: false) { - id: ID! # "initiativeAddress", e.g. "0x0000000000000000000000000000000000000000" - registered: Boolean! -} diff --git a/subgraph/src/BoldToken.mapping.ts b/subgraph/src/BoldToken.mapping.ts index 5653303b9..67e1a45ec 100644 --- a/subgraph/src/BoldToken.mapping.ts +++ b/subgraph/src/BoldToken.mapping.ts @@ -8,13 +8,27 @@ import { TroveManager as TroveManagerContract } from "../generated/BoldToken/Tro import { Collateral, CollateralAddresses } from "../generated/schema"; import { TroveManager as TroveManagerTemplate, TroveNFT as TroveNFTTemplate } from "../generated/templates"; +// Mento V3 runs one independent CDP instance per FX-pegged stable (GBPm, CHFm, +// JPYm, ...). Each instance has its own CollateralRegistry, and each registry +// starts numbering its collaterals at index 0. Naming entities by `collIndex` +// alone collides across branches — every branch would write to Collateral +// id="0", Trove id="0:", etc. +// +// We namespace every entity ID by the branch's TroveManager address (each +// branch's TroveManager is unique, and it's the address that emits the +// CollateralRegistryAddressChanged event we're handling, so it's already in +// `event.address`). The resulting collId looks like +// "0x:" and flows through DataSourceContext into +// every downstream entity ID (Trove, InterestRateBracket, InterestBatch, +// CollateralAddresses). function addCollateral( + branchPrefix: string, collIndex: i32, totalCollaterals: i32, tokenAddress: Address, troveManagerAddress: Address, ): void { - let collId = collIndex.toString(); + let collId = branchPrefix + ":" + collIndex.toString(); let collateral = new Collateral(collId); collateral.collIndex = collIndex; @@ -53,6 +67,7 @@ function addCollateral( } export function handleCollateralRegistryAddressChanged(event: CollateralRegistryAddressChangedEvent): void { + let branchPrefix = event.address.toHexString(); let registry = CollateralRegistryContract.bind(event.params._newCollateralRegistryAddress); let totalCollaterals = registry.totalCollaterals().toI32(); @@ -64,9 +79,10 @@ export function handleCollateralRegistryAddressChanged(event: CollateralRegistry break; } - // we use the token address as the id for the collateral - if (!Collateral.load(tokenAddress.toHexString())) { + let collId = branchPrefix + ":" + index.toString(); + if (!Collateral.load(collId)) { addCollateral( + branchPrefix, index, totalCollaterals, tokenAddress, diff --git a/subgraph/src/Governance.mapping.ts b/subgraph/src/Governance.mapping.ts deleted file mode 100644 index b4ffb1df3..000000000 --- a/subgraph/src/Governance.mapping.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { BigInt } from "@graphprotocol/graph-ts"; -import { - AllocateLQTY as AllocateLQTYEvent, - DepositLQTY as DepositLQTYEvent, - RegisterInitiative as RegisterInitiativeEvent, - UnregisterInitiative as UnregisterInitiativeEvent, - WithdrawLQTY as WithdrawLQTYEvent, -} from "../generated/Governance/Governance"; -import { - GovernanceAllocation, - GovernanceAllocationIndex, - GovernanceInitiative, - GovernanceVotingPower, -} from "../generated/schema"; - -export function handleRegisterInitiative(event: RegisterInitiativeEvent): void { - let initiative = new GovernanceInitiative(event.params.initiative.toHex()); - initiative.registered = true; - initiative.save(); -} - -export function handleUnregisterInitiative(event: UnregisterInitiativeEvent): void { - let initiative = GovernanceInitiative.load(event.params.initiative.toHex()); - if (initiative === null) { - throw new Error("UnregisterInitiative event for non-existing initiative"); - } - initiative.registered = false; - initiative.save(); -} - -function getVotingPower(id: string): GovernanceVotingPower { - let votingPower = GovernanceVotingPower.load(id); - - if (votingPower === null) { - votingPower = new GovernanceVotingPower(id); - votingPower.allocatedLQTY = BigInt.zero(); - votingPower.allocatedOffset = BigInt.zero(); - votingPower.unallocatedLQTY = BigInt.zero(); - votingPower.unallocatedOffset = BigInt.zero(); - } - - return votingPower; -} - -export function handleDepositLQTY(event: DepositLQTYEvent): void { - let userVotingPower = getVotingPower(event.params.user.toHex()); - let toteVotingPower = getVotingPower("total"); - - let offsetIncrease = event.params.lqtyAmount.times(event.block.timestamp); - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.plus(event.params.lqtyAmount); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.plus(event.params.lqtyAmount); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.plus(offsetIncrease); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.plus(offsetIncrease); - - userVotingPower.save(); - toteVotingPower.save(); -} - -export function handleWithdrawLQTY(event: WithdrawLQTYEvent): void { - let userVotingPower = getVotingPower(event.params.user.toHex()); - let toteVotingPower = getVotingPower("total"); - - let offsetDecrease = userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset - .times(event.params.lqtyReceived) - .div(userVotingPower.unallocatedLQTY) - : BigInt.zero(); - - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.lqtyReceived); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.lqtyReceived); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(offsetDecrease); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(offsetDecrease); - - userVotingPower.save(); - toteVotingPower.save(); -} - -class GovernanceAllocationAndIndex { - allocation: GovernanceAllocation; - allocationIndex: GovernanceAllocationIndex | null; - - constructor(allocation: GovernanceAllocation, allocationIndex: GovernanceAllocationIndex | null) { - this.allocation = allocation; - this.allocationIndex = allocationIndex; - } - - save(): void { - this.allocation.save(); - - // Workaround for AssemblyScript compiler being stupid - let allocationIndex = this.allocationIndex; - if (allocationIndex !== null) allocationIndex.save(); - } -} - -function getAllocation( - userId: string | null, - initiativeId: string, - epoch: BigInt, -): GovernanceAllocationAndIndex { - let allocationIndexId = userId !== null ? userId + ":" + initiativeId : initiativeId; - let allocationIndex: GovernanceAllocationIndex | null = null; - let allocationId = allocationIndexId + ":" + epoch.toString(); - let allocation = GovernanceAllocation.load(allocationId); - - if (allocation === null) { - allocation = new GovernanceAllocation(allocationId); - allocation.user = userId; - allocation.initiative = initiativeId; - allocation.epoch = epoch; - - allocationIndex = GovernanceAllocationIndex.load(allocationIndexId); - if (allocationIndex === null) { - allocationIndex = new GovernanceAllocationIndex(allocationIndexId); - allocationIndex.user = userId; - allocationIndex.initiative = initiativeId; - - allocation.voteLQTY = BigInt.zero(); - allocation.vetoLQTY = BigInt.zero(); - allocation.voteOffset = BigInt.zero(); - allocation.vetoOffset = BigInt.zero(); - } else { - let prevAllocation = GovernanceAllocation.load(allocationIndex.latestAllocation); - if (prevAllocation === null) { - throw new Error("GovernanceAllocation not found: " + allocationIndex.latestAllocation); - } - - allocation.voteLQTY = prevAllocation.voteLQTY; - allocation.vetoLQTY = prevAllocation.vetoLQTY; - allocation.voteOffset = prevAllocation.voteOffset; - allocation.vetoOffset = prevAllocation.vetoOffset; - } - - allocationIndex.latestAllocation = allocationId; - } - - return new GovernanceAllocationAndIndex(allocation, allocationIndex); -} - -export function handleAllocateLQTY(event: AllocateLQTYEvent): void { - let userId = event.params.user.toHex(); - let initiativeId = event.params.initiative.toHex(); - - let userVotingPower = getVotingPower(userId); - let toteVotingPower = getVotingPower("total"); - - let user = getAllocation(userId, initiativeId, event.params.atEpoch); - let tote = getAllocation(null, initiativeId, event.params.atEpoch); - - let deltaVoteOffset = event.params.deltaVoteLQTY.gt(BigInt.zero()) - ? (userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset.times(event.params.deltaVoteLQTY).div(userVotingPower.unallocatedLQTY) - : BigInt.zero()) - : (user.allocation.voteLQTY.notEqual(BigInt.zero()) - ? user.allocation.voteOffset.times(event.params.deltaVoteLQTY).div(user.allocation.voteLQTY) - : BigInt.zero()); - - let deltaVetoOffset = event.params.deltaVetoLQTY.gt(BigInt.zero()) - ? (userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset.times(event.params.deltaVetoLQTY).div(userVotingPower.unallocatedLQTY) - : BigInt.zero()) - : (user.allocation.vetoLQTY.notEqual(BigInt.zero()) - ? user.allocation.vetoOffset.times(event.params.deltaVetoLQTY).div(user.allocation.vetoLQTY) - : BigInt.zero()); - - user.allocation.voteLQTY = user.allocation.voteLQTY.plus(event.params.deltaVoteLQTY); - tote.allocation.voteLQTY = tote.allocation.voteLQTY.plus(event.params.deltaVoteLQTY); - user.allocation.vetoLQTY = user.allocation.vetoLQTY.plus(event.params.deltaVetoLQTY); - tote.allocation.vetoLQTY = tote.allocation.vetoLQTY.plus(event.params.deltaVetoLQTY); - - userVotingPower.allocatedLQTY = userVotingPower.allocatedLQTY.plus(event.params.deltaVoteLQTY); - toteVotingPower.allocatedLQTY = toteVotingPower.allocatedLQTY.plus(event.params.deltaVoteLQTY); - userVotingPower.allocatedLQTY = userVotingPower.allocatedLQTY.plus(event.params.deltaVetoLQTY); - toteVotingPower.allocatedLQTY = toteVotingPower.allocatedLQTY.plus(event.params.deltaVetoLQTY); - - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.deltaVoteLQTY); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.deltaVoteLQTY); - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.deltaVetoLQTY); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.deltaVetoLQTY); - - user.allocation.voteOffset = user.allocation.voteOffset.plus(deltaVoteOffset); - tote.allocation.voteOffset = tote.allocation.voteOffset.plus(deltaVoteOffset); - user.allocation.vetoOffset = user.allocation.vetoOffset.plus(deltaVetoOffset); - tote.allocation.vetoOffset = tote.allocation.vetoOffset.plus(deltaVetoOffset); - - userVotingPower.allocatedOffset = userVotingPower.allocatedOffset.plus(deltaVoteOffset); - toteVotingPower.allocatedOffset = toteVotingPower.allocatedOffset.plus(deltaVoteOffset); - userVotingPower.allocatedOffset = userVotingPower.allocatedOffset.plus(deltaVetoOffset); - toteVotingPower.allocatedOffset = toteVotingPower.allocatedOffset.plus(deltaVetoOffset); - - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(deltaVoteOffset); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(deltaVoteOffset); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(deltaVetoOffset); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(deltaVetoOffset); - - userVotingPower.save(); - toteVotingPower.save(); - - user.save(); - tote.save(); -} diff --git a/subgraph/src/TroveManager.mapping.ts b/subgraph/src/TroveManager.mapping.ts index cb8ed5b81..003f7922c 100644 --- a/subgraph/src/TroveManager.mapping.ts +++ b/subgraph/src/TroveManager.mapping.ts @@ -1,5 +1,5 @@ import { Address, BigInt, Bytes, dataSource, ethereum } from "@graphprotocol/graph-ts"; -import { InterestBatch, InterestRateBracket, Trove } from "../generated/schema"; +import { InterestBatch, InterestRateBracket, Trove, TroveOperation } from "../generated/schema"; import { BatchedTroveUpdated as BatchedTroveUpdatedEvent, BatchUpdated as BatchUpdatedEvent, @@ -13,19 +13,29 @@ import { const OP_OPEN_TROVE = 0; const OP_CLOSE_TROVE = 1; const OP_ADJUST_TROVE = 2; -// const OP_ADJUST_TROVE_INTEREST_RATE = 3; +const OP_ADJUST_TROVE_INTEREST_RATE = 3; const OP_APPLY_PENDING_DEBT = 4; const OP_LIQUIDATE = 5; const OP_REDEEM_COLLATERAL = 6; const OP_OPEN_TROVE_AND_JOIN_BATCH = 7; -// const OP_SET_INTEREST_BATCH_MANAGER = 8; -// const OP_REMOVE_FROM_BATCH = 9; +const OP_SET_INTEREST_BATCH_MANAGER = 8; +const OP_REMOVE_FROM_BATCH = 9; const FLASH_LOAN_TOPIC = Bytes.fromHexString( // keccak256("FlashLoan(address,address,uint256,uint256)") "0x0d7d75e01ab95780d3cd1c8ec0dd6c2ce19e3a20427eec8bf53283b6fb8e95f0", ); +const REDEMPTION_TOPIC = Bytes.fromHexString( + // keccak256("Redemption(uint256,uint256,uint256,uint256,uint256,uint256)") + "0x84ec8e1674d62e3a8ff294b1a7f53527d2d10291765fadf94e0ce431b2334334", +); + +const LIQUIDATION_TOPIC = Bytes.fromHexString( + // keccak256("Liquidation(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + "0x7243af9a1cff94d3429b2ee00b78c1c10589259f20dc167cb67704f38f9e824e", +); + function decodeAddress(data: Bytes, i: i32 = 0): ethereum.Value { return ethereum.Value.fromAddress( Address.fromBytes( @@ -210,6 +220,115 @@ export function handleTroveOperation(event: TroveOperationEvent): void { } trove.save(); + + recordTroveOperation(event, trove); +} + +// Writes one immutable TroveOperation history row per event. The preceding +// TroveUpdated / BatchedTroveUpdated handlers have already moved `trove` to +// its post-op state, so trove.debt / trove.deposit / trove.interestRate / +// trove.interestBatch can be used directly as the snapshot. +function recordTroveOperation(event: TroveOperationEvent, trove: Trove): void { + let op = new TroveOperation( + event.transaction.hash.concatI32(event.logIndex.toI32()), + ); + op.trove = trove.id; + op.blockNumber = event.block.number; + op.timestamp = event.block.timestamp; + op.transactionHash = event.transaction.hash; + op.logIndex = event.logIndex; + op.operation = troveOperationKind(event.params._operation); + op.initiator = event.transaction.from; + op.collateralDelta = event.params._collChangeFromOperation; + op.debtDelta = event.params._debtChangeFromOperation; + op.newCollateral = trove.deposit; + op.newDebt = trove.debt; + op.newInterestRate = event.params._annualInterestRate; + op.collIncreaseFromRedist = event.params._collIncreaseFromRedist; + op.debtIncreaseFromRedist = event.params._debtIncreaseFromRedist; + op.upfrontFee = event.params._debtIncreaseFromUpfrontFee; + op.batch = trove.interestBatch; + + let kind = event.params._operation; + if (kind === OP_REDEEM_COLLATERAL) { + op.redemptionPrice = extractRedemptionPrice(event); + } else if (kind === OP_LIQUIDATE) { + op.liquidationPrice = extractLiquidationPrice(event); + } + + op.save(); +} + +function troveOperationKind(opIndex: i32): string { + // Order must match ITroveEvents.Operation and the TroveOperationKind enum + // in schema.graphql. + if (opIndex === OP_OPEN_TROVE) return "openTrove"; + if (opIndex === OP_CLOSE_TROVE) return "closeTrove"; + if (opIndex === OP_ADJUST_TROVE) return "adjustTrove"; + if (opIndex === OP_ADJUST_TROVE_INTEREST_RATE) return "adjustTroveInterestRate"; + if (opIndex === OP_APPLY_PENDING_DEBT) return "applyPendingDebt"; + if (opIndex === OP_LIQUIDATE) return "liquidate"; + if (opIndex === OP_REDEEM_COLLATERAL) return "redeemCollateral"; + if (opIndex === OP_OPEN_TROVE_AND_JOIN_BATCH) return "openTroveAndJoinBatch"; + if (opIndex === OP_SET_INTEREST_BATCH_MANAGER) return "setInterestBatchManager"; + if (opIndex === OP_REMOVE_FROM_BATCH) return "removeFromBatch"; + throw new Error("Unknown TroveOperation kind: " + opIndex.toString()); +} + +// Decode the Nth uint256 from a non-indexed event's data blob. ABI lays +// non-indexed args out as packed 32-byte words in order. +function decodeUint256At(data: Bytes, wordIndex: i32): BigInt { + return BigInt.fromUnsignedBytes( + Bytes.fromUint8Array( + data.subarray(wordIndex * 32, wordIndex * 32 + 32).reverse(), + ), + ); +} + +// Redemption and Liquidation are emitted from the TroveManager that did the +// redemption / liquidation. Each Mento V3 branch (GBPm/CHFm/JPYm) has its own +// TroveManager emitting its own branch-priced events, so we must match the +// log's address to the TroveManager that emitted this TroveOperation — +// otherwise a tx touching multiple branches would attach the wrong branch's +// price. +function extractRedemptionPrice(event: TroveOperationEvent): BigInt | null { + let receipt = event.receipt; + if (!receipt) return null; + + for (let i = 0; i < receipt.logs.length; ++i) { + let log = receipt.logs[i]; + if ( + log.address.equals(event.address) + && log.topics.length > 0 + && log.topics[0].equals(REDEMPTION_TOPIC) + ) { + // Redemption(_attemptedBoldAmount, _actualBoldAmount, _ETHSent, _ETHFee, + // _price, _redemptionPrice) — _redemptionPrice is word 5. + return decodeUint256At(log.data, 5); + } + } + return null; +} + +function extractLiquidationPrice(event: TroveOperationEvent): BigInt | null { + let receipt = event.receipt; + if (!receipt) return null; + + for (let i = 0; i < receipt.logs.length; ++i) { + let log = receipt.logs[i]; + if ( + log.address.equals(event.address) + && log.topics.length > 0 + && log.topics[0].equals(LIQUIDATION_TOPIC) + ) { + // Liquidation(_debtOffsetBySP, _debtRedistributed, _boldGasCompensation, + // _collGasCompensation, _collSentToSP, _collRedistributed, + // _collSurplus, _L_ETH, _L_boldDebt, _price) + // _price is word 9. + return decodeUint256At(log.data, 9); + } + } + return null; } function inferLeverage(event: TroveOperationEvent): boolean { diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 18c7458e2..7f5591e72 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -6,8 +6,8 @@ dataSources: name: BoldToken source: abi: BoldToken - address: "0x6440f144b7e50d6a8439336510312d2f54beb01d" - startBlock: 22483043 + address: "0x3ADFF16949513480E051F451748Ee8B801731c46" + startBlock: 19187423 mapping: kind: ethereum/events apiVersion: 0.0.9 @@ -32,41 +32,73 @@ dataSources: - event: CollateralRegistryAddressChanged(address) handler: handleCollateralRegistryAddressChanged file: ./src/BoldToken.mapping.ts - network: mainnet + network: celo-sepolia - kind: ethereum/contract - name: Governance + name: BoldTokenCHFm source: - abi: Governance - address: "0x807def5e7d057df05c796f4bc75c3fe82bd6eee1" - startBlock: 22496547 + abi: BoldToken + address: "0xc21dc07eC1707d315A7eff31A6556e628E43A3f8" + startBlock: 23671277 mapping: kind: ethereum/events apiVersion: 0.0.9 language: wasm/assemblyscript entities: - - GovernanceAllocation - - GovernanceInitiative - - GovernanceUser + - Collateral + - CollateralAddresses abis: - - name: Governance - file: ../contracts/out/Governance.sol/Governance.json + - name: BorrowerOperations + file: ../contracts/out/BorrowerOperations.sol/BorrowerOperations.json + - name: BoldToken + file: ../contracts/out/BoldToken.sol/BoldToken.json + - name: CollateralRegistry + file: ../contracts/out/CollateralRegistry.sol/CollateralRegistry.json + - name: ERC20 + file: ../contracts/out/ERC20.sol/ERC20.json + - name: TroveManager + file: ../contracts/out/TroveManager.sol/TroveManager.json + - name: TroveNFT + file: ../contracts/out/TroveNFT.sol/TroveNFT.json eventHandlers: - - event: DepositLQTY(indexed - address,address,uint256,uint256,uint256,uint256,uint256) - handler: handleDepositLQTY - - event: WithdrawLQTY(indexed - address,address,uint256,uint256,uint256,uint256,uint256,uint256) - handler: handleWithdrawLQTY - - event: AllocateLQTY(indexed address,indexed address,int256,int256,uint256,uint8) - handler: handleAllocateLQTY - - event: RegisterInitiative(address,address,uint256,uint8) - handler: handleRegisterInitiative - file: ./src/Governance.mapping.ts - network: mainnet + - event: CollateralRegistryAddressChanged(address) + handler: handleCollateralRegistryAddressChanged + file: ./src/BoldToken.mapping.ts + network: celo-sepolia + - kind: ethereum/contract + name: BoldTokenJPYm + source: + abi: BoldToken + address: "0xeD54259166EbbFe72083FC37eE2a00e922fbA84D" + startBlock: 23671448 + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + entities: + - Collateral + - CollateralAddresses + abis: + - name: BorrowerOperations + file: ../contracts/out/BorrowerOperations.sol/BorrowerOperations.json + - name: BoldToken + file: ../contracts/out/BoldToken.sol/BoldToken.json + - name: CollateralRegistry + file: ../contracts/out/CollateralRegistry.sol/CollateralRegistry.json + - name: ERC20 + file: ../contracts/out/ERC20.sol/ERC20.json + - name: TroveManager + file: ../contracts/out/TroveManager.sol/TroveManager.json + - name: TroveNFT + file: ../contracts/out/TroveNFT.sol/TroveNFT.json + eventHandlers: + - event: CollateralRegistryAddressChanged(address) + handler: handleCollateralRegistryAddressChanged + file: ./src/BoldToken.mapping.ts + network: celo-sepolia templates: - name: TroveManager kind: ethereum/contract - network: mainnet + network: celo-sepolia source: abi: TroveManager mapping: @@ -102,7 +134,7 @@ templates: handler: handleBatchUpdated - name: TroveNFT kind: ethereum/contract - network: mainnet + network: celo-sepolia source: abi: TroveNFT mapping: diff --git a/subgraph/tests/trove-operation-history.test.ts b/subgraph/tests/trove-operation-history.test.ts new file mode 100644 index 000000000..e4dfc84e7 --- /dev/null +++ b/subgraph/tests/trove-operation-history.test.ts @@ -0,0 +1,361 @@ +import { Address, BigInt, Bytes, DataSourceContext, ethereum } from "@graphprotocol/graph-ts"; +import { + afterEach, + assert, + beforeEach, + clearStore, + dataSourceMock, + describe, + newMockEvent, + test, +} from "matchstick-as/assembly/index"; +import { Trove } from "../generated/schema"; +import { TroveOperation as TroveOperationEvent } from "../generated/templates/TroveManager/TroveManager"; +import { handleTroveOperation } from "../src/TroveManager.mapping"; + +const COLL_ID = "0"; +const TROVE_ID = BigInt.fromI32(1); +const TROVE_FULL_ID = COLL_ID + ":" + TROVE_ID.toHexString(); + +const REDEMPTION_TOPIC = Bytes.fromHexString( + "0x84ec8e1674d62e3a8ff294b1a7f53527d2d10291765fadf94e0ce431b2334334", +); + +const LIQUIDATION_TOPIC = Bytes.fromHexString( + "0x7243af9a1cff94d3429b2ee00b78c1c10589259f20dc167cb67704f38f9e824e", +); + +function createTrove(debt: BigInt, deposit: BigInt, interestRate: BigInt): Trove { + let trove = new Trove(TROVE_FULL_ID); + trove.borrower = Address.zero(); + trove.collateral = COLL_ID; + trove.createdAt = BigInt.fromI32(1_700_000_000); + trove.updatedAt = BigInt.fromI32(1_700_000_000); + trove.lastUserActionAt = BigInt.fromI32(1_700_000_000); + trove.mightBeLeveraged = false; + trove.status = "active"; + trove.debt = debt; + trove.deposit = deposit; + trove.stake = deposit; + trove.interestRate = interestRate; + trove.troveId = TROVE_ID.toHexString(); + trove.previousOwner = Address.zero(); + trove.redemptionCount = 0; + trove.redeemedColl = BigInt.zero(); + trove.redeemedDebt = BigInt.zero(); + trove.save(); + return trove; +} + +// ABI-encode a sequence of uint256 values into a single big-endian byte +// blob: each value becomes one 32-byte left-padded word. Mirrors what +// solidity emits as the non-indexed `data` of an event with N uint256 +// args. Used to build synthetic Redemption / Liquidation logs. +function packUint256s(values: BigInt[]): Bytes { + let hex = "0x"; + for (let i = 0; i < values.length; i++) { + let h = values[i].toHexString().slice(2); // strip "0x" + while (h.length < 64) h = "0" + h; + hex += h; + } + return Bytes.fromHexString(hex); +} + +function buildReceiptWithLog( + event: TroveOperationEvent, + topic: Bytes, + data: Bytes, +): ethereum.TransactionReceipt { + // The mapping filters receipt logs by `log.address.equals(event.address)`, + // so the synthetic log must claim to come from the same TroveManager that + // emitted the TroveOperation. event.address is the matchstick default + // address (0xA160...eC2A) unless overridden. + let log = new ethereum.Log( + event.address, + [topic], + data, + event.block.hash, + Bytes.fromUint8Array(new Uint8Array(0)), + event.transaction.hash, + BigInt.zero(), + BigInt.zero(), + BigInt.zero(), + "default", + null, + ); + return new ethereum.TransactionReceipt( + event.transaction.hash, + BigInt.zero(), + event.block.hash, + event.block.number, + BigInt.zero(), + BigInt.zero(), + Address.zero(), + [log], + BigInt.fromI32(1), + Bytes.empty(), + Bytes.empty(), + ); +} + +function newTroveOperationEvent( + operationKind: i32, + annualInterestRate: BigInt, + debtIncreaseFromRedist: BigInt, + debtIncreaseFromUpfrontFee: BigInt, + debtChangeFromOperation: BigInt, + collIncreaseFromRedist: BigInt, + collChangeFromOperation: BigInt, +): TroveOperationEvent { + let event = changetype(newMockEvent()); + event.parameters = new Array(); + event.parameters.push(new ethereum.EventParam( + "_troveId", + ethereum.Value.fromUnsignedBigInt(TROVE_ID), + )); + event.parameters.push(new ethereum.EventParam( + "_operation", + ethereum.Value.fromI32(operationKind), + )); + event.parameters.push(new ethereum.EventParam( + "_annualInterestRate", + ethereum.Value.fromUnsignedBigInt(annualInterestRate), + )); + event.parameters.push(new ethereum.EventParam( + "_debtIncreaseFromRedist", + ethereum.Value.fromUnsignedBigInt(debtIncreaseFromRedist), + )); + event.parameters.push(new ethereum.EventParam( + "_debtIncreaseFromUpfrontFee", + ethereum.Value.fromUnsignedBigInt(debtIncreaseFromUpfrontFee), + )); + event.parameters.push(new ethereum.EventParam( + "_debtChangeFromOperation", + ethereum.Value.fromSignedBigInt(debtChangeFromOperation), + )); + event.parameters.push(new ethereum.EventParam( + "_collIncreaseFromRedist", + ethereum.Value.fromUnsignedBigInt(collIncreaseFromRedist), + )); + event.parameters.push(new ethereum.EventParam( + "_collChangeFromOperation", + ethereum.Value.fromSignedBigInt(collChangeFromOperation), + )); + event.block.timestamp = BigInt.fromI32(1_700_001_000); + event.block.number = BigInt.fromI32(100); + return event; +} + +describe("handleTroveOperation history rows", () => { + beforeEach(() => { + let ctx = new DataSourceContext(); + ctx.setString("collId", COLL_ID); + dataSourceMock.setContext(ctx); + }); + + afterEach(() => { + clearStore(); + dataSourceMock.resetValues(); + }); + + test("adjustTroveInterestRate writes a history row with post-state snapshot", () => { + // Simulate the post-state that handleTroveUpdated would have left. + let postDebt = BigInt.fromString("1000000000000000000000"); // 1000e18 + let postColl = BigInt.fromString("2000000000000000000000"); // 2000e18 + let postRate = BigInt.fromString("60000000000000000"); // 6% + createTrove(postDebt, postColl, postRate); + + let event = newTroveOperationEvent( + 3, // adjustTroveInterestRate + postRate, + BigInt.zero(), + BigInt.zero(), + BigInt.zero(), + BigInt.zero(), + BigInt.zero(), + ); + handleTroveOperation(event); + + assert.entityCount("TroveOperation", 1); + let opId = event.transaction.hash.concatI32(event.logIndex.toI32()).toHexString(); + assert.fieldEquals("TroveOperation", opId, "operation", "adjustTroveInterestRate"); + assert.fieldEquals("TroveOperation", opId, "trove", TROVE_FULL_ID); + assert.fieldEquals("TroveOperation", opId, "newDebt", postDebt.toString()); + assert.fieldEquals("TroveOperation", opId, "newCollateral", postColl.toString()); + assert.fieldEquals("TroveOperation", opId, "newInterestRate", postRate.toString()); + assert.fieldEquals("TroveOperation", opId, "collateralDelta", "0"); + assert.fieldEquals("TroveOperation", opId, "debtDelta", "0"); + }); + + test("applyPendingDebt records redistribution gains without zeroing user state", () => { + let postDebt = BigInt.fromString("1100000000000000000000"); + let postColl = BigInt.fromString("2050000000000000000000"); + let postRate = BigInt.fromString("60000000000000000"); + createTrove(postDebt, postColl, postRate); + + let debtRedist = BigInt.fromString("100000000000000000000"); // +100e18 from redist + let collRedist = BigInt.fromString("50000000000000000000"); // +50e18 from redist + + let event = newTroveOperationEvent( + 4, // applyPendingDebt + postRate, + debtRedist, + BigInt.zero(), + BigInt.zero(), + collRedist, + BigInt.zero(), + ); + handleTroveOperation(event); + + let opId = event.transaction.hash.concatI32(event.logIndex.toI32()).toHexString(); + assert.fieldEquals("TroveOperation", opId, "operation", "applyPendingDebt"); + assert.fieldEquals("TroveOperation", opId, "debtIncreaseFromRedist", debtRedist.toString()); + assert.fieldEquals("TroveOperation", opId, "collIncreaseFromRedist", collRedist.toString()); + assert.fieldEquals("TroveOperation", opId, "newDebt", postDebt.toString()); + }); + + test("redeemCollateral extracts redemptionPrice from same-tx Redemption log", () => { + let postDebt = BigInt.fromString("900000000000000000000"); + let postColl = BigInt.fromString("1800000000000000000000"); + let postRate = BigInt.fromString("60000000000000000"); + createTrove(postDebt, postColl, postRate); + + let collChange = BigInt.fromString("-200000000000000000000"); // -200e18 coll + let debtChange = BigInt.fromString("-100000000000000000000"); // -100e18 debt + + let event = newTroveOperationEvent( + 6, // redeemCollateral + postRate, + BigInt.zero(), + BigInt.zero(), + debtChange, + BigInt.zero(), + collChange, + ); + + // Build a synthetic Redemption log alongside the TroveOperation in the receipt. + // Redemption(_attemptedBoldAmount, _actualBoldAmount, _ETHSent, _ETHFee, _price, _redemptionPrice) + let expectedRedemptionPrice = BigInt.fromString("1250000000000000000"); // 1.25 + let redemptionData = packUint256s([ + BigInt.fromI32(100), + BigInt.fromI32(90), + BigInt.fromI32(200), + BigInt.fromI32(5), + BigInt.fromString("1200000000000000000"), + expectedRedemptionPrice, + ]); + + event.receipt = buildReceiptWithLog(event, REDEMPTION_TOPIC, redemptionData); + + handleTroveOperation(event); + + let opId = event.transaction.hash.concatI32(event.logIndex.toI32()).toHexString(); + assert.fieldEquals("TroveOperation", opId, "operation", "redeemCollateral"); + assert.fieldEquals("TroveOperation", opId, "redemptionPrice", expectedRedemptionPrice.toString()); + assert.fieldEquals("TroveOperation", opId, "collateralDelta", collChange.toString()); + assert.fieldEquals("TroveOperation", opId, "debtDelta", debtChange.toString()); + }); + + test("redeemCollateral ignores Redemption logs emitted by a different branch's TroveManager", () => { + let postDebt = BigInt.fromString("900000000000000000000"); + let postColl = BigInt.fromString("1800000000000000000000"); + let postRate = BigInt.fromString("60000000000000000"); + createTrove(postDebt, postColl, postRate); + + let event = newTroveOperationEvent( + 6, // redeemCollateral + postRate, + BigInt.zero(), + BigInt.zero(), + BigInt.fromString("-100000000000000000000"), + BigInt.zero(), + BigInt.fromString("-200000000000000000000"), + ); + + // Stuff a Redemption log from a different TroveManager into the receipt. + // The filter must reject it; redemptionPrice should remain null. + let foreignBranchPrice = BigInt.fromString("999000000000000000000"); + let foreignData = packUint256s([ + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + foreignBranchPrice, + ]); + let foreignAddress = Address.fromString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + let foreignLog = new ethereum.Log( + foreignAddress, + [REDEMPTION_TOPIC], + foreignData, + event.block.hash, + Bytes.fromUint8Array(new Uint8Array(0)), + event.transaction.hash, + BigInt.zero(), + BigInt.zero(), + BigInt.zero(), + "default", + null, + ); + event.receipt = new ethereum.TransactionReceipt( + event.transaction.hash, + BigInt.zero(), + event.block.hash, + event.block.number, + BigInt.zero(), + BigInt.zero(), + Address.zero(), + [foreignLog], + BigInt.fromI32(1), + Bytes.empty(), + Bytes.empty(), + ); + + handleTroveOperation(event); + + let opId = event.transaction.hash.concatI32(event.logIndex.toI32()).toHexString(); + assert.fieldEquals("TroveOperation", opId, "operation", "redeemCollateral"); + // redemptionPrice should be unset (the foreign log was rejected). + assert.fieldEquals("TroveOperation", opId, "redemptionPrice", "null"); + }); + + test("liquidate extracts liquidationPrice from same-tx Liquidation log", () => { + let postDebt = BigInt.zero(); + let postColl = BigInt.zero(); + let postRate = BigInt.zero(); + createTrove(postDebt, postColl, postRate); + + let event = newTroveOperationEvent( + 5, // liquidate + BigInt.zero(), + BigInt.fromString("10000000000000000000"), + BigInt.zero(), + BigInt.fromString("-1000000000000000000000"), + BigInt.zero(), + BigInt.fromString("-2000000000000000000000"), + ); + + // Liquidation has 10 uint256 fields; _price is word 9 (zero-indexed). + let expectedLiqPrice = BigInt.fromString("1850000000000000000"); // 1.85 + let liqData = packUint256s([ + BigInt.fromI32(100), // _debtOffsetBySP + BigInt.fromI32(200), // _debtRedistributed + BigInt.fromI32(1), // _boldGasCompensation + BigInt.fromI32(2), // _collGasCompensation + BigInt.fromI32(150), // _collSentToSP + BigInt.fromI32(50), // _collRedistributed + BigInt.fromI32(10), // _collSurplus + BigInt.fromI32(0), // _L_ETH + BigInt.fromI32(0), // _L_boldDebt + expectedLiqPrice, // _price (word 9) + ]); + + event.receipt = buildReceiptWithLog(event, LIQUIDATION_TOPIC, liqData); + + handleTroveOperation(event); + + let opId = event.transaction.hash.concatI32(event.logIndex.toI32()).toHexString(); + assert.fieldEquals("TroveOperation", opId, "operation", "liquidate"); + assert.fieldEquals("TroveOperation", opId, "liquidationPrice", expectedLiqPrice.toString()); + }); +});