Skip to content
32 changes: 27 additions & 5 deletions contracts/Lens/PoolLens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,22 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
BadDebt[] badDebts;
}

/// @notice Address of the core pool comptroller (all markets are returned for this pool, including paused ones)
address public immutable corePoolComptroller;

/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
* @param corePoolComptroller_ The address of the core pool comptroller
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(bool timeBased_, uint256 blocksPerYear_) TimeManagerV8(timeBased_, blocksPerYear_) {}
constructor(
bool timeBased_,
uint256 blocksPerYear_,
address corePoolComptroller_
) TimeManagerV8(timeBased_, blocksPerYear_) {
corePoolComptroller = corePoolComptroller_;
}

/**
* @notice Queries the user's supply/borrow balances in vTokens
Expand Down Expand Up @@ -250,7 +260,7 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
address account,
address comptrollerAddress
) external view returns (RewardSummary[] memory) {
VToken[] memory markets = ComptrollerInterface(comptrollerAddress).getAllMarkets();
VToken[] memory markets = _getMarkets(ComptrollerInterface(comptrollerAddress));
RewardsDistributor[] memory rewardsDistributors = ComptrollerViewInterface(comptrollerAddress)
.getRewardDistributors();
RewardSummary[] memory rewardSummary = new RewardSummary[](rewardsDistributors.length);
Expand All @@ -276,9 +286,9 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
function getPoolBadDebt(address comptrollerAddress) external view returns (BadDebtSummary memory) {
uint256 totalBadDebtUsd;

// Get every market in the pool
// Get every listed market in the pool
ComptrollerViewInterface comptroller = ComptrollerViewInterface(comptrollerAddress);
VToken[] memory markets = comptroller.getAllMarkets();
VToken[] memory markets = _getMarkets(ComptrollerInterface(comptrollerAddress));
Comment on lines +289 to +291

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep bad-debt accounting on getAllMarkets().

A market can still carry badDebt() after it is unlisted or fully paused. Filtering with _getMarkets(...) here will understate both badDebts and totalBadDebtUsd for isolated pools—the exact markets most likely to have bad debt are the ones being dropped.

Suggested fix
-        VToken[] memory markets = _getMarkets(ComptrollerInterface(comptrollerAddress));
+        VToken[] memory markets = comptroller.getAllMarkets();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get every listed market in the pool
ComptrollerViewInterface comptroller = ComptrollerViewInterface(comptrollerAddress);
VToken[] memory markets = comptroller.getAllMarkets();
VToken[] memory markets = _getMarkets(ComptrollerInterface(comptrollerAddress));
// Get every listed market in the pool
ComptrollerViewInterface comptroller = ComptrollerViewInterface(comptrollerAddress);
VToken[] memory markets = comptroller.getAllMarkets();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/Lens/PoolLens.sol` around lines 289 - 291, The code is filtering
out unlisted/paused markets by calling _getMarkets(...), which understates
badDebt and totalBadDebtUsd; replace the filtered call with the comptroller's
full market list (call getAllMarkets() on
ComptrollerViewInterface(comptrollerAddress)) instead of
_getMarkets(ComptrollerInterface(comptrollerAddress)) so you iterate every
VToken returned (including unlisted/paused) and include each token's badDebt()
and its USD contribution when computing badDebts and totalBadDebtUsd
(referencing ComptrollerViewInterface, getAllMarkets, _getMarkets, VToken[],
comptrollerAddress, and badDebt()).

ResilientOracleInterface priceOracle = comptroller.oracle();

BadDebt[] memory badDebts = new BadDebt[](markets.length);
Expand Down Expand Up @@ -344,7 +354,7 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
// Get tokens in the Pool
ComptrollerInterface comptrollerInstance = ComptrollerInterface(venusPool.comptroller);

VToken[] memory vTokens = comptrollerInstance.getAllMarkets();
VToken[] memory vTokens = _getMarkets(comptrollerInstance);

VTokenMetadata[] memory vTokenMetadataItems = vTokenMetadataAll(vTokens);

Expand Down Expand Up @@ -453,6 +463,18 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
});
}

/**
* @notice Returns markets from a comptroller, returning an empty list for non-core-pool comptrollers
* @param comptroller The comptroller to query
* @return An array of VToken addresses
*/
function _getMarkets(ComptrollerInterface comptroller) internal view returns (VToken[] memory) {
if (address(comptroller) == corePoolComptroller) {
return comptroller.getAllMarkets();
}
return new VToken[](0);
}

function _calculateNotDistributedAwards(
address account,
VToken[] memory markets,
Expand Down
26 changes: 24 additions & 2 deletions deploy/007-deploy-pool-lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,36 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const { isTimeBased, blocksPerYear } = getBlockOrTimestampBasedDeploymentInfo(hre.getNetworkName());

await deploy("PoolLens", {
const corePoolComptroller = await deployments.get("Comptroller_Core");
const constructorArgs = [isTimeBased, blocksPerYear, corePoolComptroller.address];
Comment on lines +13 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, explore the deploy directory structure and find related files
fd . deploy/ --type f --name "*.ts" | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 322


🏁 Script executed:

# Read the specific file mentioned in the review
wc -l deploy/007-deploy-pool-lens.ts

Repository: VenusProtocol/isolated-pools

Length of output: 105


🏁 Script executed:

# Read the full context of deploy/007-deploy-pool-lens.ts
cat -n deploy/007-deploy-pool-lens.ts

Repository: VenusProtocol/isolated-pools

Length of output: 1857


🏁 Script executed:

# Search for how Comptroller_Core is used in other deploy scripts
rg "Comptroller_Core" deploy/ -A 3 -B 3

Repository: VenusProtocol/isolated-pools

Length of output: 598


🏁 Script executed:

# Check hardhat.config.ts to see network definitions and which are marked as live
cat -n hardhat.config.ts | head -100

Repository: VenusProtocol/isolated-pools

Length of output: 5299


🏁 Script executed:

# Look for skipIfAlreadyDeployed pattern in other deploy scripts
rg "skipIfAlreadyDeployed" deploy/ -B 5 -A 2

Repository: VenusProtocol/isolated-pools

Length of output: 13685


🏁 Script executed:

# Look for where Comptroller_Core is deployed
rg "Comptroller_Core" . --type ts -B 5 -A 5

Repository: VenusProtocol/isolated-pools

Length of output: 9286


🏁 Script executed:

# Check deploy/008 which deploys comptrollers
cat -n deploy/008-deploy-comptrollers.ts

Repository: VenusProtocol/isolated-pools

Length of output: 2554


🏁 Script executed:

# Search for getOrNull pattern in the codebase
rg "getOrNull|deployments\.get" deploy/ -A 2 -B 2

Repository: VenusProtocol/isolated-pools

Length of output: 5685


🏁 Script executed:

# Check the deploymentConfig to see what pools are configured
rg "Core" helpers/deploymentConfig -A 3 -B 3

Repository: VenusProtocol/isolated-pools

Length of output: 136


🏁 Script executed:

# Look for where pools are defined and if "Core" is a pool id
fd deploymentConfig.ts

Repository: VenusProtocol/isolated-pools

Length of output: 99


🏁 Script executed:

# Check if there are references to which pools should be deployed
cat -n helpers/deploymentConfig.ts | head -150

Repository: VenusProtocol/isolated-pools

Length of output: 7298


🏁 Script executed:

# Search for all pool definitions to understand pool naming
rg "pool.id|poolId" helpers/deploymentConfig.ts -B 2 -A 2

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check what getUnregisteredPools does - it determines which comptrollers get deployed
rg "getUnregisteredPools" helpers/ -A 10

Repository: VenusProtocol/isolated-pools

Length of output: 987


🏁 Script executed:

# Check deployment configurations for BSC networks to see if "Core" pool is defined
rg -A 500 "bscmainnet:" helpers/deploymentConfig.ts | head -150

Repository: VenusProtocol/isolated-pools

Length of output: 4733


🏁 Script executed:

# Check bsctestnet config
rg -A 500 "bsctestnet:" helpers/deploymentConfig.ts | head -150

Repository: VenusProtocol/isolated-pools

Length of output: 4680


🏁 Script executed:

# Look for where poolConfig gets the "Core" pool definition
rg "\"Core\"" helpers/deploymentConfig.ts

Repository: VenusProtocol/isolated-pools

Length of output: 659


🏁 Script executed:

# Check all deployment configuration files
fd deploymentConfig -t f

Repository: VenusProtocol/isolated-pools

Length of output: 99


🏁 Script executed:

# Look for where pool configs are actually defined with the Core pool
rg "id.*Core|Core.*poolConfig" . --type ts | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 934


🏁 Script executed:

# Check the full deploymentConfig to understand when Core pool is configured
wc -l helpers/deploymentConfig.ts

Repository: VenusProtocol/isolated-pools

Length of output: 104


🏁 Script executed:

# Check the section with actual pool config definitions to see if Core is always configured
sed -n '150,500p' helpers/deploymentConfig.ts

Repository: VenusProtocol/isolated-pools

Length of output: 13887


🏁 Script executed:

# Look at deploy scripts to understand execution order - hardhat-deploy executes in numeric order
ls -la deploy/ | grep "^-" | head -30

Repository: VenusProtocol/isolated-pools

Length of output: 1866


🏁 Script executed:

# Check if deploy/007 depends on deploy/008's output
rg "dependsOn|tags.*il|func.tags" deploy/007-deploy-pool-lens.ts

Repository: VenusProtocol/isolated-pools

Length of output: 103


🏁 Script executed:

# Check deploy/008 tags
rg "dependsOn|tags.*il|func.tags" deploy/008-deploy-comptrollers.ts

Repository: VenusProtocol/isolated-pools

Length of output: 107


🏁 Script executed:

# Check if Comptroller_Core might be from external deployments in venus-protocol
rg "Comptroller_Core" node_modules/@venusprotocol/venus-protocol/deployments/ 2>/dev/null | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Verify hardhat-deploy execution order - does it execute by numeric filename?
# Check if there are any explicit dependencies between 007 and 008
rg "dependsOn" deploy/007-deploy-pool-lens.ts deploy/008-deploy-comptrollers.ts

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Look at how deploy/004-swap-router handles getting Comptroller_DeFi which is deployed in 008
cat -n deploy/004-swap-router.ts | head -50

Repository: VenusProtocol/isolated-pools

Length of output: 2406


🏁 Script executed:

# Check if Comptroller_DeFi actually exists or if it's also from external deployments
rg "Comptroller_DeFi" helpers/deploymentConfig.ts -B 3 -A 3

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Search for how hardhat-deploy handles the execution when getting deployments that don't exist yet
# Check if there's any code that ensures deploy/008 runs before 007
rg "run\|tags\|il" deploy/ --type ts -B 2 -A 2 | grep -A 5 -B 5 "tags.*il"

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Look for any hardhat.config overrides for deployment order
rg "deploymentDependencies|namedAccounts|deploy.*order" hardhat.config.ts

Repository: VenusProtocol/isolated-pools

Length of output: 90


🏁 Script executed:

# Check what happens when running deploy scripts - does deploy 008 need to run first?
# Look for any documentation or test that shows execution order
fd README -o DEPLOYMENT_ORDER -o deployment.md

Repository: VenusProtocol/isolated-pools

Length of output: 223


🏁 Script executed:

# Verify which pool names map to which comptroller deployments in deploy/008
grep -n "pool.id\|pool.name\|Comptroller_" deploy/008-deploy-comptrollers.ts

Repository: VenusProtocol/isolated-pools

Length of output: 119


🏁 Script executed:

# Check if there's a specific way the scripts are meant to be run
cat -n package.json | grep -A 5 -B 5 "deploy"

Repository: VenusProtocol/isolated-pools

Length of output: 2196


🏁 Script executed:

# Check if Comptroller_Core is provided by external venus-protocol deployments or only deployed locally
rg "Comptroller_Core" node_modules/@venusprotocol/ 2>/dev/null | head -5

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Look at the actual pool configurations to confirm Core pool is in every network's config
grep -A 50 "export const globalConfig" helpers/deploymentConfig.ts | grep -A 20 "bscmainnet:"

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check if there's a deployment execution order documented or if tags affect order
cat -n deploy/006-deploy-pool-registry.ts | tail -10

Repository: VenusProtocol/isolated-pools

Length of output: 284


🏁 Script executed:

# Key insight: Check if deploy scripts that have "il" tag all need to run in sequence
# See if any deployment script references both "il" tag and a previous numbered script's deployment
rg "func.tags.*il" deploy/ -B 2 -A 5

Repository: VenusProtocol/isolated-pools

Length of output: 4164


🏁 Script executed:

# Check actual behavior - are there any snapshots or artifacts showing deployment order?
fd "deployments" --type d | head -5

Repository: VenusProtocol/isolated-pools

Length of output: 84


🏁 Script executed:

# Final confirmation: Check if hardhat-deploy truly executes numerically
# Look at the actual behavior documentation in hardhat-deploy to confirm execution order
cat -n node_modules/hardhat-deploy/package.json | grep -A 2 -B 2 "version"

Repository: VenusProtocol/isolated-pools

Length of output: 281


Use getOrNull() to handle missing Comptroller_Core on first deployment.

deployments.get("Comptroller_Core") will fail when deploying to live networks for the first time. Hardhat-deploy executes scripts numerically, so script 007 runs before script 008, which creates the Comptroller_Core deployment. On initial rollout, this artifact does not yet exist.

Possible fix
-  const corePoolComptroller = await deployments.get("Comptroller_Core");
-  const constructorArgs = [isTimeBased, blocksPerYear, corePoolComptroller.address];
+  const corePoolComptroller = await deployments.getOrNull("Comptroller_Core");
+  if (!corePoolComptroller) {
+    throw new Error("Comptroller_Core deployment not found. Run deploy/008-deploy-comptrollers.ts first.");
+  }
+  const constructorArgs = [isTimeBased, blocksPerYear, corePoolComptroller.address];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/007-deploy-pool-lens.ts` around lines 13 - 14, Replace
deployments.get("Comptroller_Core") with await
deployments.getOrNull("Comptroller_Core") and handle the null case: assign
corePoolComptrollerAddress = corePoolComptroller?.address ??
ethers.constants.AddressZero (or otherwise skip/queue this deploy) so
constructorArgs = [isTimeBased, blocksPerYear, corePoolComptrollerAddress];
ensure the code checks for a null corePoolComptroller returned by getOrNull
before accessing .address to avoid failures on first rollout.


const poolLens = await deploy("PoolLens", {
from: deployer,
args: [isTimeBased, blocksPerYear],
args: constructorArgs,
log: true,
autoMine: true,
skipIfAlreadyDeployed: true,
Comment on lines +16 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the deploy script file
cat -n deploy/007-deploy-pool-lens.ts

Repository: VenusProtocol/isolated-pools

Length of output: 1857


🏁 Script executed:

# Check if there are constructor args changes in this PR
git diff HEAD~1 deploy/007-deploy-pool-lens.ts 2>/dev/null || echo "Git diff not available, will check file directly"

Repository: VenusProtocol/isolated-pools

Length of output: 120


🏁 Script executed:

# Search for any references to "core-pool exemption" in the codebase
rg "core-pool" --type ts --type js -i

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check what PoolLens contract does and if constructor args relate to exemptions
rg "PoolLens" --type ts --type js -A 3 -B 3 | head -100

Repository: VenusProtocol/isolated-pools

Length of output: 6575


🏁 Script executed:

# Search for the PoolLens contract definition to understand the constructor
fd "PoolLens.sol" -type f

Repository: VenusProtocol/isolated-pools

Length of output: 242


🏁 Script executed:

# Search for "exemption" in the codebase to find what this refers to
rg "exemption" -i --type ts --type js --type sol -B 2 -A 2

Repository: VenusProtocol/isolated-pools

Length of output: 99


🏁 Script executed:

# Check other deploy scripts for comparison with PoolLens deployment pattern
rg "skipIfAlreadyDeployed" deploy/ --type ts -B 5 -A 5 | head -80

Repository: VenusProtocol/isolated-pools

Length of output: 5215


🏁 Script executed:

# Look for recent changes or PR context in version control
git log --oneline deploy/007-deploy-pool-lens.ts 2>/dev/null | head -5

Repository: VenusProtocol/isolated-pools

Length of output: 139


🏁 Script executed:

# Find the PoolLens contract definition
find . -name "PoolLens.sol" -o -name "PoolLens.ts" | grep -v node_modules | grep -v test

Repository: VenusProtocol/isolated-pools

Length of output: 101


🏁 Script executed:

# Search for corePoolComptroller to understand its usage
rg "corePoolComptroller" -B 3 -A 3

Repository: VenusProtocol/isolated-pools

Length of output: 50387


🏁 Script executed:

# Check what the comment says in the git commit
git show 3ba76ad --stat 2>/dev/null | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 1065


🏁 Script executed:

# Look at the PoolLens contract to understand the constructor and what the core comptroller parameter does
rg "contract PoolLens" -A 30 --type-list

Repository: VenusProtocol/isolated-pools

Length of output: 461


🏁 Script executed:

# Read the PoolLens.sol contract to understand the constructor and parameter
cat contracts/Lens/PoolLens.sol | head -150

Repository: VenusProtocol/isolated-pools

Length of output: 5183


🏁 Script executed:

# Search specifically for corePoolComptroller or coreComptroller usage in PoolLens
rg "corePoolComptroller\|coreComptroller" contracts/Lens/PoolLens.sol -A 5 -B 2

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Look for the constructor in PoolLens
ast-grep --pattern 'contract PoolLens { $$$
  constructor($_) { $$$
  }
}'

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Search for any previous versions or references to PoolLens constructor without corePoolComptroller
rg "PoolLens\(" tests/ -B 2 -A 2 | head -40

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check if there are any comments or git history about when corePoolComptroller was added
git log --all --oneline --grep="PoolLens" 2>/dev/null | head -10

Repository: VenusProtocol/isolated-pools

Length of output: 659


🏁 Script executed:

# Search for the actual usage of corePoolComptroller in PoolLens to understand the behavior
rg "corePoolComptroller" contracts/Lens/PoolLens.sol -A 10 | head -60

Repository: VenusProtocol/isolated-pools

Length of output: 1540


🏁 Script executed:

# Check if PoolLens was previously deployed and what the constructor looked like before this commit
git show 3ba76ad^:contracts/Lens/PoolLens.sol 2>/dev/null | grep -A 15 "constructor" | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check deployments directory to see if PoolLens is already deployed on existing networks
ls -la deployments/ | grep -E "^d" | head -20

Repository: VenusProtocol/isolated-pools

Length of output: 1227


🏁 Script executed:

# Check if PoolLens was deployed in prior deployment scripts
fd "deploy.*pool.*lens" deploy/ -i

Repository: VenusProtocol/isolated-pools

Length of output: 102


🏁 Script executed:

# Verify if there's an existing deployment artifact for PoolLens
find deployments -name "PoolLens.json" 2>/dev/null | head -5

Repository: VenusProtocol/isolated-pools

Length of output: 264


🏁 Script executed:

# Check what the previous version of PoolLens constructor looked like
git show 3ba76ad~1:contracts/Lens/PoolLens.sol 2>/dev/null | grep -A 20 "constructor" | head -25

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Check one of the existing deployment artifacts to see if it has the new constructor args
cat deployments/bscmainnet/PoolLens.json | jq '.args // .constructorArgs' 2>/dev/null

Repository: VenusProtocol/isolated-pools

Length of output: 95


🏁 Script executed:

# Get a clearer picture of what changed - check the diff for the PoolLens contract
git diff 3ba76ad~1 3ba76ad -- contracts/Lens/PoolLens.sol 2>/dev/null | head -80

Repository: VenusProtocol/isolated-pools

Length of output: 54


🏁 Script executed:

# Let's check the full git commit to see what all changed
git show 3ba76ad --stat 2>/dev/null | head -30

Repository: VenusProtocol/isolated-pools

Length of output: 1685


🏁 Script executed:

# Check if there's a deployment helper or if PoolLens gets deployed elsewhere or upgraded
rg "PoolLens" deploy/ --type ts -l

Repository: VenusProtocol/isolated-pools

Length of output: 102


🏁 Script executed:

# Verify that skipIfAlreadyDeployed really does skip when contract exists
rg "skipIfAlreadyDeployed" -B 5 -A 5 --type ts | grep -A 10 "deploy\(" | head -40

Repository: VenusProtocol/isolated-pools

Length of output: 95


🏁 Script executed:

# Check if there's an upgrade mechanism for PoolLens
rg "upgrade\|Upgrade" deploy/007-deploy-pool-lens.ts -i

Repository: VenusProtocol/isolated-pools

Length of output: 54


Constructor signature change blocks deployment on networks with existing PoolLens.

The PR adds corePoolComptroller as a required constructor parameter (changing signature from 2 to 3 args), but skipIfAlreadyDeployed: true prevents redeployment on existing networks. PoolLens is already deployed on mainnet chains (BSC, Ethereum, Arbitrum, etc.) with the old 2-parameter constructor. When this deploy script runs on those networks, it will skip entirely, leaving the old deployment in place without the corePoolComptroller parameter. This means the core-pool market filtering logic won't activate on those networks.

Either remove skipIfAlreadyDeployed: true to allow re-deployment, or implement an upgrade path using a proxy if storage changes are a concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/007-deploy-pool-lens.ts` around lines 16 - 21, The deployment
currently calls deploy("PoolLens", ...) with skipIfAlreadyDeployed: true which
prevents updating existing PoolLens instances after adding the new required
constructor arg corePoolComptroller; update the deploy logic so networks with an
existing PoolLens get the new constructor value: either remove
skipIfAlreadyDeployed from the deploy(...) call to force redeployment with the
new constructorArgs (ensuring deployer/args are correct), or implement an
explicit upgrade path (use a proxy/upgrade mechanism to replace PoolLens
implementation while preserving storage) and wire corePoolComptroller into the
new implementation; locate references to PoolLens, constructorArgs,
corePoolComptroller, and skipIfAlreadyDeployed to make the change.

});

if (poolLens.newlyDeployed) {
console.log("Verifying PoolLens...");
try {
await hre.run("verify:verify", {
address: poolLens.address,
constructorArguments: constructorArgs,
});
console.log("PoolLens verified successfully");
} catch (error: any) {
if (error.message.includes("Already Verified")) {
console.log("PoolLens already verified");
} else {
console.error("Verification failed:", error.message);
}
}
}
};

func.tags = ["PoolLens", "il"];
func.skip = async (hre: HardhatRuntimeEnvironment) => !hre.network.live;

export default func;
160 changes: 160 additions & 0 deletions tests/hardhat/Fork/PoolLensForkTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import chai from "chai";
import { ethers } from "hardhat";

import {
Comptroller,
Comptroller__factory,
PoolLens,
PoolLens__factory,
PoolRegistry__factory,
} from "../../../typechain";
import { getContractAddresses, setForkBlock } from "./utils";

const { expect } = chai;

const FORK = process.env.FORK === "true";
const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet";

const { POOL_REGISTRY, COMPTROLLER, CORE_COMPTROLLER, ACC1, BLOCK_NUMBER } = getContractAddresses(
FORKED_NETWORK as string,
);

// BSC uses block-based, Arbitrum/Optimism/Base/zkSync use time-based
const TIME_BASED_NETWORKS = [
"arbitrumone",
"arbitrumsepolia",
"zksyncmainnet",
"zksyncsepolia",
"opmainnet",
"opsepolia",
"basemainnet",
"basesepolia",
"unichainmainnet",
"unichainsepolia",
];

const isTimeBased = TIME_BASED_NETWORKS.includes(FORKED_NETWORK);
const BSC_BLOCKS_PER_YEAR = 70_080_000;
const ETH_BLOCKS_PER_YEAR = 2_628_000;
const OPBNB_BLOCKS_PER_YEAR = 126_144_000;

function getBlocksPerYear(network: string): number {
if (TIME_BASED_NETWORKS.includes(network)) return 0;
if (network.includes("opbnb")) return OPBNB_BLOCKS_PER_YEAR;
if (network === "ethereum" || network === "sepolia") return ETH_BLOCKS_PER_YEAR;
return BSC_BLOCKS_PER_YEAR;
}

if (FORK) {
describe(`PoolLens Fork Test (${FORKED_NETWORK})`, () => {
let poolLens: PoolLens;
let comptroller: Comptroller;

before(async () => {
await setForkBlock(BLOCK_NUMBER);

const poolLensFactory = (await ethers.getContractFactory("PoolLens")) as PoolLens__factory;
poolLens = await poolLensFactory.deploy(
isTimeBased,
getBlocksPerYear(FORKED_NETWORK),
CORE_COMPTROLLER || ethers.constants.AddressZero,
);
await poolLens.deployed();

comptroller = Comptroller__factory.connect(COMPTROLLER, ethers.provider);
});

describe("getAllPools", () => {
it("should not revert", async () => {
const pools = await poolLens.getAllPools(POOL_REGISTRY);
expect(pools.length).to.be.greaterThan(0);

for (const pool of pools) {
expect(pool.comptroller).to.not.equal(ethers.constants.AddressZero);
}
});
});

describe("getPoolByComptroller", () => {
it("should not revert and return markets for core pool or empty list for non-core pool", async () => {
const pool = await poolLens.getPoolByComptroller(POOL_REGISTRY, COMPTROLLER);
expect(pool.comptroller).to.equal(COMPTROLLER);

if (COMPTROLLER === CORE_COMPTROLLER) {
expect(pool.vTokens.length).to.be.greaterThan(0);
for (const vToken of pool.vTokens) {
expect(vToken.isListed).to.equal(true);
}
} else {
expect(pool.vTokens.length).to.equal(0);
}
});
});

describe("getPendingRewards", () => {
it("should not revert", async () => {
const account = ACC1 || ethers.constants.AddressZero;
const rewards = await poolLens.getPendingRewards(account, COMPTROLLER);
expect(rewards).to.be.an("array");
});
});

describe("getPoolBadDebt", () => {
it("should not revert", async () => {
const badDebtSummary = await poolLens.getPoolBadDebt(COMPTROLLER);
expect(badDebtSummary.comptroller).to.equal(COMPTROLLER);
expect(badDebtSummary.badDebts).to.be.an("array");
for (const badDebt of badDebtSummary.badDebts) {
expect(badDebt.vTokenAddress).to.not.equal(ethers.constants.AddressZero);
}
});
});

describe("vTokenMetadataAll", () => {
it("should not revert for markets in pool", async () => {
const allMarkets = await comptroller.getAllMarkets();

if (allMarkets.length > 0) {
const metadata = await poolLens.vTokenMetadataAll(allMarkets);
expect(metadata.length).to.equal(allMarkets.length);
}
});
});

describe("getPoolDataFromVenusPool", () => {
it("should not revert", async () => {
const poolRegistry = PoolRegistry__factory.connect(POOL_REGISTRY, ethers.provider);
const venusPool = await poolRegistry.getPoolByComptroller(COMPTROLLER);
const poolData = await poolLens.getPoolDataFromVenusPool(POOL_REGISTRY, venusPool);
expect(poolData.comptroller).to.equal(COMPTROLLER);
});
});

describe("non-core pool returns empty list", () => {
it("should return empty vTokens for non-core pool comptrollers", async () => {
if (COMPTROLLER === CORE_COMPTROLLER) {
return;
}

const pool = await poolLens.getPoolByComptroller(POOL_REGISTRY, COMPTROLLER);
expect(pool.vTokens.length).to.equal(0);
});

it("should include all markets for core pool", async () => {
if (!CORE_COMPTROLLER || CORE_COMPTROLLER === ethers.constants.AddressZero) {
return;
}

const coreComptroller = Comptroller__factory.connect(CORE_COMPTROLLER, ethers.provider);
const allMarkets = await coreComptroller.getAllMarkets();
const pool = await poolLens.getPoolByComptroller(POOL_REGISTRY, CORE_COMPTROLLER);
const returnedAddresses = pool.vTokens.map(v => v.vToken.toLowerCase());

expect(returnedAddresses.length).to.equal(allMarkets.length);
for (const market of allMarkets) {
expect(returnedAddresses).to.include(market.toLowerCase());
}
});
});
});
}
32 changes: 29 additions & 3 deletions tests/hardhat/Fork/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const contractAddresses = {
VTOKEN1: SepoliaContracts.VToken_vcrvUSD_Core.address,
VTOKEN2: SepoliaContracts.VToken_vCRV_Core.address,
COMPTROLLER: SepoliaContracts.Comptroller_Core.address,
CORE_COMPTROLLER: "0x7Aa39ab4BcA897F403425C9C6FDbd0f882Be0D70",
PSR: PsrSepTestnet.contracts.ProtocolShareReserve.address,
REWARD_DISTRIBUTOR1: SepoliaContracts.RewardsDistributor_Core_1.address,
POOL_REGISTRY: SepoliaContracts.PoolRegistry.address,
Expand All @@ -60,6 +61,7 @@ export const contractAddresses = {
VTOKEN1: EthereumContracts.VToken_vcrvUSD_Curve.address,
VTOKEN2: EthereumContracts.VToken_vCRV_Curve.address,
COMPTROLLER: EthereumContracts.Comptroller_Curve.address,
CORE_COMPTROLLER: "0x687a01ecF6d3907658f7A7c714749fAC32336D1B",
PSR: PsrEthereum.contracts.ProtocolShareReserve.address,
REWARD_DISTRIBUTOR1: EthereumContracts.RewardsDistributor_Curve_0.address,
POOL_REGISTRY: EthereumContracts.PoolRegistry.address,
Expand All @@ -70,7 +72,7 @@ export const contractAddresses = {
ACC1: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
ACC2: "0x29182006a4967e9a50C0A66076dA514993D3B4D4",
ACC3: "0xa27CEF8aF2B6575903b676e5644657FAe96F491F",
BLOCK_NUMBER: 19781700,
BLOCK_NUMBER: 24770000,
},
bsctestnet: {
ADMIN: GovernanceBscTestnet.contracts.NormalTimelock.address,
Expand All @@ -80,6 +82,7 @@ export const contractAddresses = {
VTOKEN1: TestnetContracts.VToken_vUSDD_Stablecoins.address,
VTOKEN2: TestnetContracts.VToken_vlisUSD_Stablecoins.address,
COMPTROLLER: TestnetContracts.Comptroller_Stablecoins.address,
CORE_COMPTROLLER: "0x0000000000000000000000000000000000000000",
PSR: PsrBscTestnet.contracts.ProtocolShareReserve.address,
SHORTFALL: TestnetContracts.Shortfall.address,
RISKFUND: PsrBscTestnet.contracts.RiskFundV2.address,
Expand All @@ -104,6 +107,7 @@ export const contractAddresses = {
VTOKEN1: MainnetContracts.VToken_vUSDD_Stablecoins.address,
VTOKEN2: MainnetContracts.VToken_vlisUSD_Stablecoins.address,
COMPTROLLER: MainnetContracts.Comptroller_Stablecoins.address,
CORE_COMPTROLLER: "0x0000000000000000000000000000000000000000",
PSR: PsrBscMainnet.contracts.ProtocolShareReserve.address,
SHORTFALL: MainnetContracts.Shortfall.address,
RISKFUND: PsrBscMainnet.contracts.RiskFundV2.address,
Expand All @@ -130,6 +134,7 @@ export const contractAddresses = {
VTOKEN1: OpBnbTestnetContracts.VToken_vBTCB_Core.address,
VTOKEN2: OpBnbTestnetContracts.VToken_vETH_Core.address,
COMPTROLLER: OpBnbTestnetContracts.Comptroller_Core.address,
CORE_COMPTROLLER: "0x2FCABb31E57F010D623D8d68e1E18Aed11d5A388",
PSR: PsrOpBnbTestnet.address,
POOL_REGISTRY: OpBnbTestnetContracts.PoolRegistry.address,
RESILIENT_ORACLE: OracleOpBnbTestnet.contracts.ResilientOracle.address,
Expand All @@ -150,6 +155,7 @@ export const contractAddresses = {
VTOKEN1: OpBnbMainnetContracts.VToken_vUSDT_Core.address,
VTOKEN2: OpBnbMainnetContracts.VToken_vFDUSD_Core.address,
COMPTROLLER: OpBnbMainnetContracts.Comptroller_Core.address,
CORE_COMPTROLLER: "0xD6e3E2A1d8d95caE355D15b3b9f8E5c2511874dd",
PSR: "0xDDc9017F3073aa53a4A8535163b0bf7311F72C52",
POOL_REGISTRY: OpBnbMainnetContracts.PoolRegistry.address,
RESILIENT_ORACLE: OracleOpBnbMainnet.contracts.ResilientOracle.address,
Expand All @@ -161,14 +167,15 @@ export const contractAddresses = {
ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1",
ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef",
ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3",
BLOCK_NUMBER: 17881611,
BLOCK_NUMBER: 127330000,
},
arbitrumsepolia: {
ADMIN: "0x1426A5Ae009c4443188DA8793751024E358A61C2",
ACM: GovernanceArbSep.contracts.AccessControlManager.address,
VTOKEN1: ArbSepContracts.VToken_vWETH_Core.address,
VTOKEN2: ArbSepContracts.VToken_vARB_Core.address,
COMPTROLLER: ArbSepContracts.Comptroller_Core.address,
CORE_COMPTROLLER: "0x006D44b6f5927b3eD83bD0c1C36Fb1A3BaCaC208",
PSR: PsrArbSep.contracts.ProtocolShareReserve.address,
REWARD_DISTRIBUTOR1: ArbSepContracts.RewardsDistributor_Core_0.address,
POOL_REGISTRY: ArbSepContracts.PoolRegistry.address,
Expand All @@ -189,6 +196,7 @@ export const contractAddresses = {
VTOKEN1: ArbOneContracts.VToken_vWETH_Core.address,
VTOKEN2: ArbOneContracts.VToken_vARB_Core.address,
COMPTROLLER: ArbOneContracts.Comptroller_Core.address,
CORE_COMPTROLLER: "0x317c1A5739F39046E20b08ac9BeEa3f10fD43326",
PSR: PsrArbOne.contracts.ProtocolShareReserve.address,
REWARD_DISTRIBUTOR1: ArbOneContracts.RewardsDistributor_Core_0.address,
POOL_REGISTRY: ArbOneContracts.PoolRegistry.address,
Expand All @@ -201,6 +209,24 @@ export const contractAddresses = {
ACC1: "0x32B701d3957fee432664cFA57FB44b0fE8496659",
ACC2: "0xB09F16F625B363875e39ADa56C03682088471523",
ACC3: "0x4A2339eE9c4fD4c99DE1d3AeB513B53ab42Db5ca",
BLOCK_NUMBER: 224198807,
BLOCK_NUMBER: 447530000,
},
basemainnet: {
CORE_COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
POOL_REGISTRY: "0xeef902918DdeCD773D4B422aa1C6e1673EB9136F",
ACC1: "0xcdac0d6c6c59727a65f871236188350531885c43",
BLOCK_NUMBER: 44070000,
},
opmainnet: {
CORE_COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
POOL_REGISTRY: "0x147780799840d541C1d7c998F0cbA996d11D62bb",
ACC1: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A",
BLOCK_NUMBER: 149670000,
},
unichainmainnet: {
CORE_COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
POOL_REGISTRY: "0x0C52403E16BcB8007C1e54887E1dFC1eC9765D7C",
ACC1: "0x0000000000000000000000000000000000000001",
BLOCK_NUMBER: 44190000,
},
Comment on lines +214 to 231

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing COMPTROLLER field will cause test failures.

The new network entries (basemainnet, opmainnet, unichainmainnet) are missing the COMPTROLLER field, but PoolLensForkTest.ts destructures and uses it at multiple locations (lines 64, 80, 104, 127). Running tests with FORKED_NETWORK=basemainnet (or the other new networks) will cause runtime errors.

If these networks have a single core pool, COMPTROLLER should likely equal CORE_COMPTROLLER.

Proposed fix
   basemainnet: {
     CORE_COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
+    COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
     POOL_REGISTRY: "0xeef902918DdeCD773D4B422aa1C6e1673EB9136F",
     ACC1: "0xcdac0d6c6c59727a65f871236188350531885c43",
     BLOCK_NUMBER: 44070000,
   },
   opmainnet: {
     CORE_COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
+    COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
     POOL_REGISTRY: "0x147780799840d541C1d7c998F0cbA996d11D62bb",
     ACC1: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A",
     BLOCK_NUMBER: 149670000,
   },
   unichainmainnet: {
     CORE_COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
+    COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
     POOL_REGISTRY: "0x0C52403E16BcB8007C1e54887E1dFC1eC9765D7C",
     ACC1: "0x0000000000000000000000000000000000000001",
     BLOCK_NUMBER: 44190000,
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
basemainnet: {
CORE_COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
POOL_REGISTRY: "0xeef902918DdeCD773D4B422aa1C6e1673EB9136F",
ACC1: "0xcdac0d6c6c59727a65f871236188350531885c43",
BLOCK_NUMBER: 44070000,
},
opmainnet: {
CORE_COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
POOL_REGISTRY: "0x147780799840d541C1d7c998F0cbA996d11D62bb",
ACC1: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A",
BLOCK_NUMBER: 149670000,
},
unichainmainnet: {
CORE_COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
POOL_REGISTRY: "0x0C52403E16BcB8007C1e54887E1dFC1eC9765D7C",
ACC1: "0x0000000000000000000000000000000000000001",
BLOCK_NUMBER: 44190000,
},
basemainnet: {
CORE_COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
COMPTROLLER: "0x0C7973F9598AA62f9e03B94E92C967fD5437426C",
POOL_REGISTRY: "0xeef902918DdeCD773D4B422aa1C6e1673EB9136F",
ACC1: "0xcdac0d6c6c59727a65f871236188350531885c43",
BLOCK_NUMBER: 44070000,
},
opmainnet: {
CORE_COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
COMPTROLLER: "0x5593FF68bE84C966821eEf5F0a988C285D5B7CeC",
POOL_REGISTRY: "0x147780799840d541C1d7c998F0cbA996d11D62bb",
ACC1: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A",
BLOCK_NUMBER: 149670000,
},
unichainmainnet: {
CORE_COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
COMPTROLLER: "0xe22af1e6b78318e1Fe1053Edbd7209b8Fc62c4Fe",
POOL_REGISTRY: "0x0C52403E16BcB8007C1e54887E1dFC1eC9765D7C",
ACC1: "0x0000000000000000000000000000000000000001",
BLOCK_NUMBER: 44190000,
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hardhat/Fork/constants.ts` around lines 214 - 231, The three new
network objects (basemainnet, opmainnet, unichainmainnet) are missing the
COMPTROLLER key which PoolLensForkTest (it destructures COMPTROLLER at multiple
places) expects; add a COMPTROLLER property to each network object in the
constants (e.g., set COMPTROLLER to the same value as CORE_COMPTROLLER if the
network has a single core pool) so PoolLensForkTest.ts can destructure and use
COMPTROLLER without runtime errors.

};
2 changes: 1 addition & 1 deletion tests/hardhat/Lens/PoolLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ for (const isTimeBased of [false, true]) {
.setActionsPaused([vWBTC.address], [ACTION_LIQUIDATE, ACTION_EXIT_MARKET], true);

const PoolLens = await ethers.getContractFactory<PoolLens__factory>("PoolLens");
poolLens = await PoolLens.deploy(isTimeBased, slotsPerYear);
poolLens = await PoolLens.deploy(isTimeBased, slotsPerYear, ethers.constants.AddressZero);
});

describe(`${description}PoolView Tests`, () => {
Expand Down
Loading