-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add ri quote calculation #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 3 commits
bf8dbab
e51a1ff
ddc1cbe
7a7ea9a
8cc21a8
6da220f
faf04a0
7aca887
8211011
762f397
99ffd7f
29e7e5a
964435c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,11 @@ | ||||||||||
| const { ethers } = require('ethers'); | ||||||||||
| const { BigNumber, ethers } = require('ethers'); | ||||||||||
|
|
||||||||||
| const constants = require('./constants'); | ||||||||||
|
|
||||||||||
| const { WeiPerEther } = ethers.constants; | ||||||||||
| const { defaultAbiCoder } = ethers.utils; | ||||||||||
|
|
||||||||||
| const createChainApi = async contracts => { | ||||||||||
| const createChainApi = async (contracts, riContracts) => { | ||||||||||
| // contract instances | ||||||||||
| const cover = contracts('Cover'); | ||||||||||
| const coverProducts = contracts('CoverProducts'); | ||||||||||
|
|
@@ -108,6 +112,83 @@ const createChainApi = async contracts => { | |||||||||
| return packedTrancheAllocation; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // RiContracts | ||||||||||
| const fetchVaultStake = async (vaultId, subnetworks = [], productId = null, riSubnetworks = {}) => { | ||||||||||
| const operator = '0x51ad1265C8702c9e96Ea61Fe4088C2e22eD4418e'; | ||||||||||
| let maxWeightedStake = BigNumber.from(0); | ||||||||||
|
|
||||||||||
| for (const subnetworkId of subnetworks) { | ||||||||||
| const subnetworkStake = await riContracts[`delegator_${vaultId}`].stake(subnetworkId, operator); | ||||||||||
|
|
||||||||||
| // Determine the weight to use for this subnetwork | ||||||||||
| let weight = constants.RI_WEIGHT; // Default weight | ||||||||||
|
|
||||||||||
| if (productId !== null && riSubnetworks && riSubnetworks[subnetworkId]) { | ||||||||||
| const subnetwork = riSubnetworks[subnetworkId]; | ||||||||||
| // Check if this subnetwork contains the product | ||||||||||
| if (subnetwork.products && subnetwork.products[String(productId)]) { | ||||||||||
| weight = subnetwork.products[String(productId)].weight; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Calculate weighted stake for this subnetwork: stake * weight / 100 | ||||||||||
| const weightedStake = subnetworkStake.mul(weight).div(constants.RI_WEIGHT_DENOMINATOR); | ||||||||||
|
|
||||||||||
| // Keep track of the maximum weighted stake across all subnetworks | ||||||||||
| // This allows a subnetwork with lower stake but higher weight to win | ||||||||||
| maxWeightedStake = weightedStake.gt(maxWeightedStake) ? weightedStake : maxWeightedStake; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return maxWeightedStake; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const fetchVaultWithdrawals = async vaultId => { | ||||||||||
| console.log(riContracts[`vault_${vaultId}`].address); | ||||||||||
| const currentEpoch = await riContracts[`vault_${vaultId}`].currentEpoch(); | ||||||||||
| const withdrawalAmount = await riContracts[`vault_${vaultId}`].withdrawals(currentEpoch + 1); | ||||||||||
|
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In ethers.js v5, an ethers.BigNumber cannot be incremented using the + operator like currentEpoch + 1. Instead, the method currentEpoch.add(1) must be used. The official ethers.js v5 documentation lists arithmetic operations as methods on the BigNumber class: bigNumber.add(otherValue), bigNumber.sub(otherValue), etc. There is no mention of operator overloading for +, which JavaScript classes do not support natively. This contrasts with v6, where BigNumber is replaced by native BigInt, which does support +. Migration docs explicitly show v5 using .add(value2) vs v6 using value1 + value2. Examples attempting native JS operations on BigNumber-related values (e.g., formatted units) show + concatenates as strings, confirming no arithmetic operator support. Citations:
Replace In ethers.js v5, Suggested fix- const withdrawalAmount = await riContracts[`vault_${vaultId}`].withdrawals(currentEpoch + 1);
+ const withdrawalAmount = await riContracts[`vault_${vaultId}`].withdrawals(currentEpoch.add(1));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| return withdrawalAmount.mul(constants.RI_WEIGHT).div(constants.RI_WEIGHT_DENOMINATOR); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const fetchVaultAllocations = async blockNumber => { | ||||||||||
| const events = await cover.queryFilter(cover.filters.CoverRiAllocated(), blockNumber); | ||||||||||
|
|
||||||||||
| const allocations = {}; | ||||||||||
| for (const event of events) { | ||||||||||
| const { args } = event; | ||||||||||
| const { coverId, data, dataFormat } = args; | ||||||||||
|
|
||||||||||
| const { start, period, productId, originalCoverId } = await fetchCover(coverId); | ||||||||||
| const coverAllocations = defaultAbiCoder.decode([constants.RI_DATA_FORMATS[dataFormat]], [data]); | ||||||||||
|
|
||||||||||
|
Comment on lines
+160
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In ethers.js v5, defaultAbiCoder.decode(['tuple(uint256 amount,uint256 vaultId,uint256 subnetworkId,uint256 providerId)[]'], data) returns a Result object, which is an array subclass containing a single element: an array of arrays. Each inner array represents one tuple (struct) with four elements corresponding to amount, vaultId, subnetworkId, and providerId (as BigNumber objects). If the ABI types had names, the Result would also provide named access (e.g., result[0][0].amount), but since the provided type string lacks names, access is positional only. The data should be passed directly as a hex string (e.g., '0x...'), not wrapped in [data]. The decode method expects the raw ABI-encoded bytes for the specified types; wrapping it in an array would treat it as an encoded array containing that data, which is incorrect for a single top-level array return value. Examples from docs show decode returns nested arrays for tuple and array types, e.g.: - decode(["uint[]", "string"], data) returns [ [1234n, 5678n], "Hello World" ] - decode(["uint", "tuple(uint256, string)"], data) returns [ 1234n, [ 5678n, "Hello World" ] ] For tuple(...)[], it analogously returns [ [ [amt0, vault0, sub0, prov0], [amt1, vault1, sub1, prov1], ... ] ]. Use result[0] to access the array of tuples. Citations:
🏁 Script executed: fd -t f chainApi.jsRepository: NexusMutual/cover-router Length of output: 110 🏁 Script executed: cat -n src/lib/chainApi.js | sed -n '155,170p'Repository: NexusMutual/cover-router Length of output: 816 🏁 Script executed: rg -n "decodeRiData" --type jsRepository: NexusMutual/cover-router Length of output: 372 🏁 Script executed: rg -n "coverAllocations" src/lib/chainApi.js -A 3 -B 1Repository: NexusMutual/cover-router Length of output: 506 🏁 Script executed: rg -n "defaultAbiCoder.decode" src/lib/chainApi.js -A 2 -B 2Repository: NexusMutual/cover-router Length of output: 336 🏁 Script executed: cat -n src/lib/helpers.js | sed -n '300,325p'Repository: NexusMutual/cover-router Length of output: 868 🏁 Script executed: rg -n "RI_DATA_FORMATS" --type js -B 2 -A 5Repository: NexusMutual/cover-router Length of output: 1748 🏁 Script executed: cat -n src/lib/synchronizer.js | sed -n '130,140p'Repository: NexusMutual/cover-router Length of output: 683 🏁 Script executed: cat -n src/lib/constants.js | sed -n '60,65p'Repository: NexusMutual/cover-router Length of output: 275 🏁 Script executed: rg -n "coverAllocations\|allocations" src/lib/chainApi.js -A 2 -B 1 | head -40Repository: NexusMutual/cover-router Length of output: 50 🏁 Script executed: fd -t f "test.*\.js" | head -10Repository: NexusMutual/cover-router Length of output: 50 🏁 Script executed: rg -n "decodeRiData\|RI_DATA_FORMATS" test/ --type jsRepository: NexusMutual/cover-router Length of output: 50 Fix The encode bytes should be passed directly as Suggested fixesIn chainApi.js (line 161): - const coverAllocations = defaultAbiCoder.decode([constants.RI_DATA_FORMATS[dataFormat]], [data]);
+ const [coverAllocations] = defaultAbiCoder.decode([constants.RI_DATA_FORMATS[dataFormat]], data);In helpers.js (line 307): -const decodeRiData = (data, dataFormat) => {
- return defaultAbiCoder.decode([RI_DATA_FORMATS[dataFormat]], [data]);
-};
+const decodeRiData = (data, dataFormat) => {
+ const [allocations] = defaultAbiCoder.decode([RI_DATA_FORMATS[dataFormat]], data);
+ return allocations;
+};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| for (const coverAllocation of coverAllocations) { | ||||||||||
| const { amount, vaultId } = coverAllocation; | ||||||||||
| if (!allocations[vaultId]) { | ||||||||||
| allocations[`${productId}_${vaultId}`] = []; | ||||||||||
| } | ||||||||||
MilGard91 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| allocations[`${productId}_${vaultId}`].push({ | ||||||||||
| amount, | ||||||||||
| coverId, | ||||||||||
| expiryTimestamp: start + period, | ||||||||||
MilGard91 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
| originalCoverId, | ||||||||||
| }); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return allocations; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const fetchVaultNextEpochStart = async vaultId => { | ||||||||||
| return await riContracts[`vault_${vaultId}`].nextEpochStart(); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const fetchRiAssetRate = async assetId => { | ||||||||||
| return { | ||||||||||
| assetRate: await riContracts[`asset_${assetId}`].getRate(), | ||||||||||
| protocolAssetCorrelationId: riContracts[`asset_${assetId}`].protocolAssetCorrelationId, | ||||||||||
| }; | ||||||||||
|
Comment on lines
+185
to
+189
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In ethers.js v5, Solidity public variable getters must be invoked as functions on a Contract instance, using contract.foo rather than contract.foo. Public state variables in Solidity automatically generate view getter functions in the ABI. Ethers.js treats these as regular view functions, requiring the function call syntax with parentheses and await. Examples: - Simple public uint: await contract.totalAllocPoint (not contract.totalAllocPoint) - Public array: await contract.poolInfo(2) - Public mapping: await contract.orderByAssetId(address, id) Direct property access like contract.foo (without ) is not supported and will not trigger the blockchain call. Using Human-Readable ABI requires defining getters explicitly as functions, e.g., 'function totalAllocPoint view returns (uint256)'. This behavior is consistent across ethers.js v5 documentation, GitHub discussions by maintainers, and Stack Exchange answers confirming the getter function invocation. Citations:
🏁 Script executed: # Check the actual code at lines 185-189
sed -n '185,189p' src/lib/chainApi.jsRepository: NexusMutual/cover-router Length of output: 293 🏁 Script executed: # Search for other potential instances of protocolAssetCorrelationId usage
rg 'protocolAssetCorrelationId' src/lib/chainApi.js -B 2 -A 2Repository: NexusMutual/cover-router Length of output: 252 🏁 Script executed: # Check the context around the function to understand the broader impact
sed -n '180,195p' src/lib/chainApi.jsRepository: NexusMutual/cover-router Length of output: 501 Invoke In ethers.js v5, public Solidity state variables automatically generate view getter functions that require function call syntax with parentheses and Suggested fix return {
assetRate: await riContracts[`asset_${assetId}`].getRate(),
- protocolAssetCorrelationId: riContracts[`asset_${assetId}`].protocolAssetCorrelationId,
+ protocolAssetCorrelationId: await riContracts[`asset_${assetId}`].protocolAssetCorrelationId(),
};🤖 Prompt for AI Agents |
||||||||||
| }; | ||||||||||
|
|
||||||||||
| return { | ||||||||||
| fetchProducts, | ||||||||||
| fetchProduct, | ||||||||||
|
|
@@ -122,6 +203,11 @@ const createChainApi = async contracts => { | |||||||||
| fetchCover, | ||||||||||
| fetchCoverPoolTrancheAllocations, | ||||||||||
| fetchCoverReference, | ||||||||||
| fetchVaultStake, | ||||||||||
| fetchVaultWithdrawals, | ||||||||||
| fetchVaultAllocations, | ||||||||||
| fetchVaultNextEpochStart, | ||||||||||
| fetchRiAssetRate, | ||||||||||
| }; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,21 @@ module.exports = { | |
| SECONDS_PER_DAY: BigNumber.from(24 * 60 * 60), | ||
|
|
||
| // set to minimal cover id which is not expired | ||
| FETCH_COVER_DATA_FROM_ID: 992, | ||
| FETCH_COVER_DATA_FROM_ID: 3710, | ||
|
Comment on lines
48
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't bootstrap from a moving cover floor.
🤖 Prompt for AI Agents |
||
|
|
||
| // Ri constants | ||
| SYMBIOTIC_PROVIDER_ID: 1, | ||
| RI_FETCH_COVER_DATA_FROM_BLOCK: 24219565, | ||
| RI_WEIGHT: 25, | ||
| RI_WEIGHT_DENOMINATOR: 100, | ||
| RI_THRESHOLD: 20_000_000 * 1_000_000, // in USDC | ||
| RI_MIN_COVER_AMOUNT: 100_000 * 1_000_000, // in USDC | ||
| RI_COVER_AMOUNT_PERCENTAGE: 80, | ||
| RI_COVER_AMOUNT_DENOMINATOR: 100, | ||
| RI_EPOCH_DURATION: 70, // days | ||
|
Comment on lines
+54
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want some of these as env var config? |
||
|
|
||
| RI_DATA_FORMATS: { | ||
| 1: 'tuple(uint256 amount,uint256 vaultId, uint256 subnetworkId,uint256 providerId)[]', | ||
| }, | ||
| HTTP_STATUS, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,12 +13,14 @@ const { | |||||||||||||||||||||
| CAPACITY_BUFFER_MINIMUM, | ||||||||||||||||||||||
| MAX_ACTIVE_TRANCHES, | ||||||||||||||||||||||
| HTTP_STATUS, | ||||||||||||||||||||||
| RI_DATA_FORMATS, | ||||||||||||||||||||||
| } = require('./constants'); | ||||||||||||||||||||||
| const { ApiError } = require('./error'); | ||||||||||||||||||||||
| const { selectCover } = require('../store/selectors'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const { BigNumber } = ethers; | ||||||||||||||||||||||
| const { WeiPerEther, Zero } = ethers.constants; | ||||||||||||||||||||||
| const { defaultAbiCoder } = ethers.utils; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* Bignumber Utils */ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -301,6 +303,10 @@ function getLatestCover(store, originalCoverId) { | |||||||||||||||||||||
| : selectCover(store, originalCover.latestCoverId); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const decodeRiData = (data, dataFormat) => { | ||||||||||||||||||||||
| return defaultAbiCoder.decode([RI_DATA_FORMATS[dataFormat]], [data]); | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
Comment on lines
+306
to
+308
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In ethers.js v5.x, the exact signature of ethers.utils.defaultAbiCoder.decode is abiCoder.decode(types, data) => Result, where 'types' is an array of strings or ParamType objects describing the expected output types, and 'data' is a single BytesLike value (such as a hex string prefixed with '0x' containing the encoded ABI data). The second argument should be a BytesLike value, not an array containing the encoded payload. All examples in the official documentation pass a single hex string as the second parameter, such as ethers.utils.defaultAbiCoder.decode(["uint", "string"], "0x000000..."). Citations:
Fix the The Suggested fix const decodeRiData = (data, dataFormat) => {
- return defaultAbiCoder.decode([RI_DATA_FORMATS[dataFormat]], [data]);
+ const type = RI_DATA_FORMATS[dataFormat];
+ if (!type) {
+ throw new Error(`Unsupported RI data format: ${dataFormat}`);
+ }
+ return defaultAbiCoder.decode([type], data);
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| module.exports = { | ||||||||||||||||||||||
| bnMax, | ||||||||||||||||||||||
| bnMin, | ||||||||||||||||||||||
|
|
@@ -319,4 +325,5 @@ module.exports = { | |||||||||||||||||||||
| getCoverTrancheAllocations, | ||||||||||||||||||||||
| calculateCoverRefundInNXM, | ||||||||||||||||||||||
| getLatestCover, | ||||||||||||||||||||||
| decodeRiData, | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: NexusMutual/cover-router
Length of output: 520
Add missing
providerUrlconfig key to prevent RI signing failures.src/lib/signQuote.js(line 33) readsconfig.get('providerUrl'), but the config schema atsrc/config.jsonly declaresprovider(line 20). In strict config mode, this causes a failure on the first RI signing request.Fix
provider: { doc: 'Providers URL', env: 'PROVIDER_URL', default: '', }, + providerUrl: { + doc: 'Providers URL (alias used by RI signer)', + env: 'PROVIDER_URL', + default: '', + },🤖 Prompt for AI Agents