diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 55cd9cdd..fea74be6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,6 +47,38 @@ jobs: - name: Run Matchstick tests run: yarn test +integration-test-core-pool: + runs-on: ubuntu-20.04 + needs: unit-test + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Start containers + run: docker compose up -d + + - name: Sleep to allow graph-node to become accessible + shell: bash + run: sleep 45s + + - name: Generate Core Pool graphql types + run: | + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run prepare:docker + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run codegen + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run create:docker + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run deploy:docker + sleep 5s + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run generate-subgraph-types + + - name: Run Core Pool integration tests + run: | + docker exec -i subgraph-hardhat-node yarn workspace venus-subgraph run test:integration --bail + + - name: Stop containers + if: always() + run: | + docker compose down -v + docker system prune -f -a --volumes integration-test-isolated-pools: runs-on: ubuntu-20.04 diff --git a/copy_contracts.sh b/copy_contracts.sh index 13ec08c6..a166e15a 100755 --- a/copy_contracts.sh +++ b/copy_contracts.sh @@ -36,3 +36,8 @@ rm ./contracts/governance/contracts/Governance/Timelock.sol mkdir -p ./contracts/mocks cp -rf ./mocks/ ./contracts/mocks/contracts +mv ./contracts/mocks/contracts/VBep20DelegateR1.sol ./contracts/protocol/contracts/Tokens/VTokens/legacy/VBep20DelegateR1.sol +mv ./contracts/mocks/contracts/VBep20DelegatorR1.sol ./contracts/protocol/contracts/Tokens/VTokens/legacy/VBep20DelegatorR1.sol + +mkdir -p ./contracts/utilities +cp -rf ./node_modules/@venusprotocol/solidity-utilities/contracts ./contracts/utilities diff --git a/deploy/000-core-pool.ts b/deploy/000-core-pool.ts index 466c1c03..5b9e57af 100644 --- a/deploy/000-core-pool.ts +++ b/deploy/000-core-pool.ts @@ -12,12 +12,21 @@ import deployMockTokens from '@venusprotocol/venus-protocol/dist/deploy/007-depl import deployXvs from '@venusprotocol/venus-protocol/dist/deploy/007-deploy-xvs'; import deployVaults from '@venusprotocol/venus-protocol/dist/deploy/008-deploy-vaults'; import configureVaults from '@venusprotocol/venus-protocol/dist/deploy/009-configure-vaults'; -import deployMarkets from '@venusprotocol/venus-protocol/dist/deploy/011-deploy-markets'; +// import deployMarkets from '@venusprotocol/venus-protocol/dist/deploy/011-deploy-markets'; import deployPrime from '@venusprotocol/venus-protocol/dist/deploy/012-deploy-prime'; import deployConfigurePrime from '@venusprotocol/venus-protocol/dist/deploy/013-configure-prime'; -import deployVaiController from '@venusprotocol/venus-protocol/dist/deploy/013-vai-controller-impl'; -import deployTokenRedeemer from '@venusprotocol/venus-protocol/dist/deploy/014-deploy-token-redeemer'; -import { DeployFunction } from 'hardhat-deploy/types'; +import deployVaiController from '@venusprotocol/venus-protocol/dist/deploy/014-vai-controller-deploy'; +import setupVaiController from '@venusprotocol/venus-protocol/dist/deploy/014-vai-controller-set-config'; +import deployTokenRedeemer from '@venusprotocol/venus-protocol/dist/deploy/015-deploy-token-redeemer'; +import { + InterestRateModels, + getConfig, + getTokenConfig, +} from '@venusprotocol/venus-protocol/dist/helpers/deploymentConfig'; +import { BigNumber, BigNumberish } from 'ethers'; +import { parseUnits } from 'ethers/lib/utils'; +import { ethers } from 'hardhat'; +import { DeployFunction, DeployResult } from 'hardhat-deploy/types'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { @@ -35,10 +44,185 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await deployXvs(hre); await deployVaults(hre); await configureVaults(hre); - await deployMarkets(hre); + // await deployMarkets(hre); + + const mantissaToBps = (num: BigNumberish) => { + return BigNumber.from(num).div(parseUnits('1', 14)).toString(); + }; + + const VTOKEN_DECIMALS = 8; + const EMPTY_BYTES_ARRAY = '0x'; + + const { deployments, getNamedAccounts } = hre; + const { deploy } = deployments; + + const { deployer } = await getNamedAccounts(); + const { tokensConfig, marketsConfig } = await getConfig(hre.network.name); + + const comptrollerDeployment = await deployments.get('Unitroller'); + const vTreasuryDeployment = await deployments.get('VTreasuryV8'); + + console.log(`Got deployment of Unitroller with address: ${comptrollerDeployment.address}`); + + await deploy('MockUSDT', { + contract: 'MockToken', + from: deployer, + args: ['Tether', 'USDT', 18], + log: true, + autoMine: true, + }); + + await deploy('MockDOGE', { + contract: 'MockToken', + from: deployer, + args: ['Doge', 'DOGE', 18], + log: true, + autoMine: true, + }); + + const extraMarkets = [ + { + name: 'Venus DOGE', + asset: 'DOGE', + symbol: 'vDOGE', + rateModel: InterestRateModels.JumpRate.toString(), + baseRatePerYear: '0', + multiplierPerYear: parseUnits('0.06875', 18), + jumpMultiplierPerYear: parseUnits('2.5', 18), + kink_: parseUnits('0.8', 18), + collateralFactor: parseUnits('0.9', 18), + liquidationThreshold: parseUnits('0.95', 18), + reserveFactor: parseUnits('0.1', 18), + initialSupply: parseUnits('9000', 18), + supplyCap: parseUnits('5500000000', 18), + borrowCap: parseUnits('4400000000', 18), + vTokenReceiver: vTreasuryDeployment.address, + }, + { + name: 'Venus USDT', + asset: 'USDT', + symbol: 'vUSDT', + rateModel: InterestRateModels.JumpRate.toString(), + baseRatePerYear: '0', + multiplierPerYear: parseUnits('0.06875', 18), + jumpMultiplierPerYear: parseUnits('2.5', 18), + kink_: parseUnits('0.8', 18), + collateralFactor: parseUnits('0.9', 18), + liquidationThreshold: parseUnits('0.95', 18), + reserveFactor: parseUnits('0.1', 18), + initialSupply: parseUnits('9000', 18), + supplyCap: parseUnits('5500000', 18), + borrowCap: parseUnits('4400000', 18), + vTokenReceiver: vTreasuryDeployment.address, + }, + ]; + + for (const market of [...marketsConfig, ...extraMarkets]) { + const { + name, + asset, + symbol, + rateModel, + baseRatePerYear, + multiplierPerYear, + jumpMultiplierPerYear, + kink_, + } = market; + + const token = getTokenConfig(asset, [ + ...tokensConfig, + { + isMock: true, + name: 'DOGE', + symbol: 'DOGE', + decimals: 18, + }, + { + isMock: true, + name: 'USDT', + symbol: 'USDT', + decimals: 18, + }, + ]); + let tokenContract; + if (token.isMock) { + tokenContract = await ethers.getContract(`Mock${token.symbol}`); + } else { + tokenContract = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + token.tokenAddress, + ); + } + + let rateModelAddress: string; + if (rateModel === InterestRateModels.JumpRate.toString()) { + const [b, m, j, k] = [baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_].map( + mantissaToBps, + ); + const rateModelName = `JumpRateModel_base${b}bps_slope${m}bps_jump${j}bps_kink${k}bps`; + console.log(`Deploying interest rate model ${rateModelName}`); + const result: DeployResult = await deploy(rateModelName, { + from: deployer, + contract: 'JumpRateModel', + args: [baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_], + log: true, + }); + rateModelAddress = result.address; + } else { + const [b, m] = [baseRatePerYear, multiplierPerYear].map(mantissaToBps); + const rateModelName = `WhitePaperInterestRateModel_base${b}bps_slope${m}bps`; + console.log(`Deploying interest rate model ${rateModelName}`); + const result: DeployResult = await deploy(rateModelName, { + from: deployer, + contract: 'WhitePaperInterestRateModel', + args: [baseRatePerYear, multiplierPerYear], + log: true, + }); + rateModelAddress = result.address; + } + + const vBep20DelegateDeployment = await deploy('VBep20DelegateR1', { from: deployer }); + + const underlyingDecimals = Number(await tokenContract.decimals()); + const vTokenImplemenationAddress = vBep20DelegateDeployment.address; + + console.log( + `Deploying VBep20 Proxy for ${symbol} with Implementation ${vTokenImplemenationAddress}`, + ); + + await deploy(`${symbol}`, { + contract: 'VBep20DelegatorR1', + from: deployer, + args: [ + tokenContract.address, + comptrollerDeployment.address, + rateModelAddress, + parseUnits('1', underlyingDecimals + 18 - VTOKEN_DECIMALS), + name, + symbol, + VTOKEN_DECIMALS, + deployer, + vTokenImplemenationAddress, + EMPTY_BYTES_ARRAY, + ], + log: true, + }); + } + const deployerSigner = await ethers.getSigner(deployer); + + const comptroller = await ethers.getContractAt('ComptrollerMock', comptrollerDeployment.address); + + const vDogeToken = await ethers.getContract('vDOGE'); + const vFdusdToken = await ethers.getContract('vFDUSD'); + const vUsdtToken = await ethers.getContract('vUSDT'); + await comptroller.connect(deployerSigner)._supportMarket(vDogeToken.address); + await comptroller.connect(deployerSigner)._supportMarket(vFdusdToken.address); + await comptroller.connect(deployerSigner)._supportMarket(vUsdtToken.address); + await deployPrime(hre); await deployConfigurePrime(hre); await deployVaiController(hre); + await setupVaiController(hre); await deployTokenRedeemer(hre); }; diff --git a/deploy/002-deploy-oracles.ts b/deploy/002-deploy-oracles.ts index b4bff90e..b6311125 100644 --- a/deploy/002-deploy-oracles.ts +++ b/deploy/002-deploy-oracles.ts @@ -1,3 +1,16 @@ -import deployOracle from '@venusprotocol/oracle/dist/deploy/1-deploy-oracles'; +import { DeployFunction } from 'hardhat-deploy/types'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; -export default deployOracle; +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployments, getNamedAccounts } = hre; + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('MockPriceOracleUnderlyingPrice', { + from: deployer, + log: true, + autoMine: true, + }); +}; + +export default func; diff --git a/deploy/018-setup-core-markets.ts b/deploy/018-setup-core-markets.ts new file mode 100644 index 00000000..97e77c7a --- /dev/null +++ b/deploy/018-setup-core-markets.ts @@ -0,0 +1,163 @@ +import { mine } from '@nomicfoundation/hardhat-network-helpers'; +import { ethers } from 'hardhat'; +import { DeployFunction } from 'hardhat-deploy/types'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const { parseUnits } = ethers.utils; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployer: root } = await hre.getNamedAccounts(); + const rootSigner = ethers.provider.getSigner(root); + const comptroller = await ethers.getContract('Unitroller'); + + // Set Access Control + const acm = await ethers.getContract('AccessControlManager'); + const vUsdcToken = await ethers.getContract('vUSDC'); + const vWBnbToken = await ethers.getContract('vBNB'); + const vEthToken = await ethers.getContract('vETH'); + + let vFdusdToken = await ethers.getContract('vFDUSD'); + let vDogeToken = await ethers.getContract('vDOGE'); + let vUsdtToken = await ethers.getContract('vUSDT'); + + await acm + .connect(rootSigner) + .giveCallPermission(comptroller.address, '_setMarketSupplyCaps(address[],uint256[])', root); + await acm + .connect(rootSigner) + .giveCallPermission(comptroller.address, '_setMarketBorrowCaps(address[],uint256[])', root); + await acm + .connect(rootSigner) + .giveCallPermission(comptroller.address, '_setCollateralFactor(address,uint256)', root); + await acm + .connect(rootSigner) + .giveCallPermission(comptroller.address, '_setCloseFactor(uint256)', root); + + await vUsdcToken.connect(rootSigner).setAccessControlManager(acm.address); + await vWBnbToken.connect(rootSigner).setAccessControlManager(acm.address); + await vEthToken.connect(rootSigner).setAccessControlManager(acm.address); + + await acm + .connect(rootSigner) + .giveCallPermission(vUsdcToken.address, '_setReserveFactor(uint256)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vWBnbToken.address, '_setReserveFactor(uint256)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vEthToken.address, '_setReserveFactor(uint256)', root); + + await acm + .connect(rootSigner) + .giveCallPermission(vUsdtToken.address, '_setReserveFactor(uint256)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vDogeToken.address, '_setReserveFactor(uint256)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vFdusdToken.address, '_setReserveFactor(uint256)', root); + + await acm + .connect(rootSigner) + .giveCallPermission(vUsdtToken.address, 'setProtocolShareReserve(address)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vDogeToken.address, 'setProtocolShareReserve(address)', root); + await acm + .connect(rootSigner) + .giveCallPermission(vFdusdToken.address, 'setProtocolShareReserve(address)', root); + + vDogeToken = await ethers.getContractAt('VToken', vDogeToken.address); + vFdusdToken = await ethers.getContractAt('VToken', vFdusdToken.address); + vUsdtToken = await ethers.getContractAt('VToken', vUsdtToken.address); + await vUsdtToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vDogeToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vFdusdToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + + await comptroller._setComptrollerLens((await ethers.getContract('ComptrollerLens')).address); + + await mine(1); + + await vUsdcToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vWBnbToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vEthToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + + await vUsdtToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vDogeToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + await vFdusdToken.setProtocolShareReserve( + ( + await ethers.getContract('ProtocolShareReserve') + ).address, + ); + + await vUsdcToken.accrueInterest(); + await vUsdcToken._setReserveFactor(parseUnits('0.1')); + + await vWBnbToken.accrueInterest(); + await vWBnbToken._setReserveFactor(parseUnits('0.1')); + + await vEthToken.accrueInterest(); + await vEthToken._setReserveFactor(parseUnits('0.1')); + + // Set Prices + const oracle = await ethers.getContract('MockPriceOracleUnderlyingPrice'); + await comptroller._setPriceOracle(oracle.address); + // // USDC $1 + await oracle.setPrice(vUsdcToken.address, parseUnits('1', 18).toString()); + // // BNB 500 + await oracle.setPrice(vWBnbToken.address, parseUnits('500', 18).toString()); + // // ETH $5000 + await oracle.setPrice(vEthToken.address, parseUnits('5000', 18).toString()); + + await comptroller._supportMarket(vUsdcToken.address); + await comptroller._supportMarket(vWBnbToken.address); + await comptroller._supportMarket(vEthToken.address); + + await comptroller._setCollateralFactor(vUsdcToken.address, parseUnits('0.9')); + await comptroller._setCollateralFactor(vWBnbToken.address, parseUnits('0.9')); + await comptroller._setCollateralFactor(vEthToken.address, parseUnits('0.9')); + + await comptroller._setMarketSupplyCaps( + [vUsdcToken.address, vWBnbToken.address, vEthToken.address], + [parseUnits('500000'), parseUnits('500'), parseUnits('500')], + ); + await comptroller._setMarketBorrowCaps( + [vUsdcToken.address, vWBnbToken.address, vEthToken.address], + [parseUnits('500000'), parseUnits('500'), parseUnits('500')], + ); + await comptroller._setCloseFactor(parseUnits('0.1')); +}; + +export default func; diff --git a/hardhat.config.ts b/hardhat.config.ts index ada4c325..8e53c842 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,5 +1,6 @@ import 'module-alias/register'; import 'hardhat-deploy'; +import "@nomicfoundation/hardhat-chai-matchers"; import "@nomiclabs/hardhat-ethers"; import 'hardhat-dependency-compiler'; import { HardhatUserConfig } from 'hardhat/config'; @@ -60,6 +61,12 @@ const config: HardhatUserConfig = { hardhat: { chainId: 56, allowUnlimitedContractSize: true, + mining: { + auto: true, + mempool: { + order: "fifo" + } + } }, }, paths: { @@ -72,9 +79,17 @@ const config: HardhatUserConfig = { // Hardhat deploy namedAccounts: { deployer: 0, // here this will by default take the first account as deployer - acc1: 1, - acc2: 2, - proxyAdmin: 3, + supplier1: 1, + supplier2: 2, + supplier3: 3, + borrower1: 4, + borrower2: 5, + borrower3: 6, + liquidator1: 7, + liquidator2: 8, + liquidator3: 9, + acc1: 10, + acc2: 11 } }; diff --git a/mocks/MockPriceOracleUnderlyingPrice.sol b/mocks/MockPriceOracleUnderlyingPrice.sol index 8e6a70bb..1f17a7ae 100644 --- a/mocks/MockPriceOracleUnderlyingPrice.sol +++ b/mocks/MockPriceOracleUnderlyingPrice.sol @@ -1,7 +1,11 @@ pragma solidity 0.8.25; import { ResilientOracleInterface } from '../../oracle/contracts/interfaces/OracleInterface.sol'; -import 'hardhat/console.sol'; + +contract IVToken { + address public underlying; +} + contract MockPriceOracleUnderlyingPrice is ResilientOracleInterface { mapping(address => uint256) public prices; @@ -16,12 +20,18 @@ contract MockPriceOracleUnderlyingPrice is ResilientOracleInterface { prices[asset] = prices[asset]; } - function setPrice(address vToken, uint256 price) public { + function setPrice(address vToken, uint256 price) external { + address asset = IVToken(vToken).underlying(); prices[vToken] = price; + prices[asset] = price; + } + + function getAssetTokenAmount(address vToken, uint256 value) external view returns (uint256) { + return value / prices[vToken]; } function getPrice(address asset) external view returns (uint256) { - return 0; + return prices[asset]; } function getUnderlyingPrice(address vToken) external view returns (uint256) { diff --git a/mocks/VBep20DelegateR1.sol b/mocks/VBep20DelegateR1.sol new file mode 100644 index 00000000..f6f7da2c --- /dev/null +++ b/mocks/VBep20DelegateR1.sol @@ -0,0 +1,45 @@ +pragma solidity ^0.5.16; + +import "../VTokenInterfaces.sol"; +import { VBep20R1 } from "./VBep20R1.sol"; +import { VDelegateInterface } from "./VTokenInterfaceR1.sol"; + +/** + * @title Venus's VBep20Delegate Contract + * @notice VTokens which wrap an EIP-20 underlying and are delegated to + * @author Venus + */ +contract VBep20DelegateR1 is VBep20R1, VDelegateInterface { + /** + * @notice Construct an empty delegate + */ + constructor() public {} + + /** + * @notice Called by the delegator on a delegate to initialize it for duty + * @param data The encoded bytes data for any initialization + */ + function _becomeImplementation(bytes memory data) public { + // Shh -- currently unused + data; + + // Shh -- we don't ever want this hook to be marked pure + if (false) { + implementation = address(0); + } + + require(msg.sender == admin, "only the admin may call _becomeImplementation"); + } + + /** + * @notice Called by the delegator on a delegate to forfeit its responsibility + */ + function _resignImplementation() public { + // Shh -- we don't ever want this hook to be marked pure + if (false) { + implementation = address(0); + } + + require(msg.sender == admin, "only the admin may call _resignImplementation"); + } +} diff --git a/mocks/VBep20DelegatorR1.sol b/mocks/VBep20DelegatorR1.sol new file mode 100644 index 00000000..fd88d1ee --- /dev/null +++ b/mocks/VBep20DelegatorR1.sol @@ -0,0 +1,528 @@ +pragma solidity ^0.5.16; +import "../VTokenInterfaces.sol"; +import "./VTokenInterfaceR1.sol"; +import "hardhat/console.sol"; +/** + * @title Venus's VBep20Delegator Contract + * @notice vTokens which wrap an EIP-20 underlying and delegate to an implementation + * @author Venus + */ +contract VBep20DelegatorR1 is VTokenInterfaceR1, VBep20Interface, VDelegatorInterface { + /** + * @notice Construct a new money market + * @param underlying_ The address of the underlying asset + * @param comptroller_ The address of the comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ BEP-20 name of this token + * @param symbol_ BEP-20 symbol of this token + * @param decimals_ BEP-20 decimal precision of this token + * @param admin_ Address of the administrator of this token + * @param implementation_ The address of the implementation the contract delegates to + * @param becomeImplementationData The encoded args for becomeImplementation + */ + constructor( + address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + address implementation_, + bytes memory becomeImplementationData + ) public { + // Creator of the contract is admin during initialization + admin = msg.sender; + + // First delegate gets to initialize the delegator (i.e. storage contract) + delegateTo( + implementation_, + abi.encodeWithSignature( + "initialize(address,address,address,uint256,string,string,uint8)", + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_ + ) + ); + + // New implementations always get set via the settor (post-initialize) + _setImplementation(implementation_, false, becomeImplementationData); + + // Set the proper admin now that initialization is done + admin = admin_; + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + */ + function() external payable { + require(msg.value == 0, "VBep20Delegator:fallback: cannot send value to fallback"); + + // delegate all other functions to current implementation + (bool success, ) = implementation.delegatecall(msg.data); + + assembly { + let free_mem_ptr := mload(0x40) + returndatacopy(free_mem_ptr, 0, returndatasize) + + switch success + case 0 { + revert(free_mem_ptr, returndatasize) + } + default { + return(free_mem_ptr, returndatasize) + } + } + } + + /** + * @notice Sender supplies assets into the market and receives vTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function mint(uint mintAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("mint(uint256)", mintAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender supplies assets into the market and receiver receives vTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function mintBehalf(address receiver, uint mintAmount) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("mintBehalf(address,uint256)", receiver, mintAmount) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems vTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of vTokens to redeem into underlying asset + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function redeem(uint redeemTokens) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("redeem(uint256)", redeemTokens)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems vTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to redeem + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function redeemUnderlying(uint redeemAmount) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("redeemUnderlying(uint256)", redeemAmount) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function borrow(uint borrowAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrow(uint256)", borrowAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function repayBorrow(uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("repayBorrow(uint256)", repayAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays a borrow belonging to another borrower + * @param borrower The account with the debt being payed off + * @param repayAmount The amount to repay + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("repayBorrowBehalf(address,uint256)", borrower, repayAmount) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this vToken to be liquidated + * @param vTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function liquidateBorrow( + address borrower, + uint repayAmount, + VTokenInterface vTokenCollateral + ) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("liquidateBorrow(address,uint256,address)", borrower, repayAmount, vTokenCollateral) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("transfer(address,uint256)", dst, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("transferFrom(address,address,uint256)", src, dst, amount) + ); + return abi.decode(data, (bool)); + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("approve(address,uint256)", spender, amount) + ); + return abi.decode(data, (bool)); + } + + /** + * @notice Get the underlying balance of the `owner` + * @dev This also accrues interest in a transaction + * @param owner The address of the account to query + * @return The amount of underlying owned by `owner` + */ + function balanceOfUnderlying(address owner) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("balanceOfUnderlying(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return The total borrows with interest + */ + function totalBorrowsCurrent() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("totalBorrowsCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return The calculated balance + */ + function borrowBalanceCurrent(address account) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrowBalanceCurrent(address)", account)); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Will fail unless called by another vToken during the process of liquidation. + * It's absolutely critical to use msg.sender as the borrowed vToken and not a parameter. + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of vTokens to seize + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("seize(address,address,uint256)", liquidator, borrower, seizeTokens) + ); + return abi.decode(data, (uint)); + } + + /*** Admin Functions ***/ + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("_setPendingAdmin(address)", newPendingAdmin) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh + * @dev Admin function to accrue interest and set a new reserve factor + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("_setReserveFactor(uint256)", newReserveFactorMantissa) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Accepts transfer of admin rights. `msg.sender` must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _acceptAdmin() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_acceptAdmin()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and adds reserves by transferring from admin + * @param addAmount Amount of reserves to add + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _addReserves(uint addAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_addReserves(uint256)", addAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and reduces reserves by transferring to admin + * @param reduceAmount Amount of reduction to reserves + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _reduceReserves(uint reduceAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_reduceReserves(uint256)", reduceAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get cash balance of this vToken in the underlying asset + * @return The quantity of underlying asset owned by this contract + */ + function getCash() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getCash()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint) { + bytes memory data = delegateToViewImplementation( + abi.encodeWithSignature("allowance(address,address)", owner, spender) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the token balance of the `owner` + * @param owner The address of the account to query + * @return The number of tokens owned by `owner` + */ + function balanceOf(address owner) external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("balanceOf(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get a snapshot of the account's balances and the cached exchange rate + * @dev This is used by comptroller to more efficiently perform liquidity checks. + * @param account Address of the account to snapshot + * @return (possible error, token balance, borrow balance, exchange rate mantissa) + */ + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { + bytes memory data = delegateToViewImplementation( + abi.encodeWithSignature("getAccountSnapshot(address)", account) + ); + return abi.decode(data, (uint, uint, uint, uint)); + } + + /** + * @notice Returns the current per-block borrow interest rate for this vToken + * @return The borrow interest rate per block, scaled by 1e18 + */ + function borrowRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("borrowRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current per-block supply interest rate for this vToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("supplyRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Called by the admin to update the implementation of the delegator + * @param implementation_ The address of the new implementation for delegation + * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation + * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation + */ + // @custom:access Only callable by admin + function _setImplementation( + address implementation_, + bool allowResign, + bytes memory becomeImplementationData + ) public { + require(msg.sender == admin, "VBep20Delegator::_setImplementation: Caller must be admin"); + + if (allowResign) { + delegateToImplementation(abi.encodeWithSignature("_resignImplementation()")); + } + + address oldImplementation = implementation; + implementation = implementation_; + + delegateToImplementation(abi.encodeWithSignature("_becomeImplementation(bytes)", becomeImplementationData)); + + emit NewImplementation(oldImplementation, implementation); + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateCurrent() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("exchangeRateCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Applies accrued interest to total borrows and reserves. + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + */ + function accrueInterest() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("accrueInterest()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Sets a new comptroller for the market + * @dev Admin function to set a new comptroller + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _setComptroller(ComptrollerInterface newComptroller) public returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("_setComptroller(address)", newComptroller) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and updates the interest rate model using `_setInterestRateModelFresh` + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel The new interest rate model to use + * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details). + */ + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { + bytes memory data = delegateToImplementation( + abi.encodeWithSignature("_setInterestRateModel(address)", newInterestRateModel) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Delegates execution to the implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToImplementation(bytes memory data) public returns (bytes memory) { + return delegateTo(implementation, data); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop. + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).staticcall( + abi.encodeWithSignature("delegateToImplementation(bytes)", data) + ); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return abi.decode(returnData, (bytes)); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return The calculated balance + */ + function borrowBalanceStored(address account) public view returns (uint) { + bytes memory data = delegateToViewImplementation( + abi.encodeWithSignature("borrowBalanceStored(address)", account) + ); + return abi.decode(data, (uint)); + } + + /** + * @notice Calculates the exchange rate from the underlying to the VToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateStored() public view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("exchangeRateStored()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Internal method to delegate execution to another contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param callee The contract to delegatecall + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateTo(address callee, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = callee.delegatecall(data); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return returnData; + } +} diff --git a/package.json b/package.json index fff47057..91bf7902 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,14 @@ "test": "yarn workspaces foreach run test", "node:integration": "yarn hardhat node --hostname 0.0.0.0", "test:integration": "yarn workspaces foreach run test:integration", - "postinstall": "yarn patch-package && ./copy_contracts.sh" + "postinstall": "yarn patch-package" }, "devDependencies": { "@chainlink/contracts": "^0.5.1", "@graphprotocol/client-cli": "3.0.0", "@graphprotocol/graph-cli": "^0.73.0", "@graphprotocol/graph-ts": "^0.32.0", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", "@nomicfoundation/hardhat-network-helpers": "^1.0.4", "@nomiclabs/hardhat-ethers": "^2.1.1", "@nomiclabs/hardhat-etherscan": "^3.1.0", @@ -51,7 +52,7 @@ "@venusprotocol/isolated-pools": "3.3.0", "@venusprotocol/oracle": "2.2.0", "@venusprotocol/protocol-reserve": "2.3.0-dev.1", - "@venusprotocol/venus-protocol": "9.0.0-dev.6", + "@venusprotocol/venus-protocol": "9.2.0-dev.2", "assemblyscript": "0.19.23", "chai": "^4.3.6", "eslint": "^8.25.0", diff --git a/patches/@venusprotocol+governance-contracts+2.0.0.patch b/patches/@venusprotocol+governance-contracts+2.1.0.patch similarity index 100% rename from patches/@venusprotocol+governance-contracts+2.0.0.patch rename to patches/@venusprotocol+governance-contracts+2.1.0.patch diff --git a/patches/@venusprotocol+venus-protocol+9.0.0-dev.6.patch b/patches/@venusprotocol+venus-protocol+9.2.0-dev.2.patch similarity index 96% rename from patches/@venusprotocol+venus-protocol+9.0.0-dev.6.patch rename to patches/@venusprotocol+venus-protocol+9.2.0-dev.2.patch index 221ec6a9..1d408b7c 100644 --- a/patches/@venusprotocol+venus-protocol+9.0.0-dev.6.patch +++ b/patches/@venusprotocol+venus-protocol+9.2.0-dev.2.patch @@ -267,6 +267,18 @@ index 7dd98f1..964ae46 100644 struct MintLocalVars { MathError mathErr; uint exchangeRateMantissa; +diff --git a/node_modules/@venusprotocol/venus-protocol/contracts/Tokens/VTokens/legacy/VTokenInterfaceR1.sol b/node_modules/@venusprotocol/venus-protocol/contracts/Tokens/VTokens/legacy/VTokenInterfaceR1.sol +index 7a058db..82c6352 100644 +--- a/node_modules/@venusprotocol/venus-protocol/contracts/Tokens/VTokens/legacy/VTokenInterfaceR1.sol ++++ b/node_modules/@venusprotocol/venus-protocol/contracts/Tokens/VTokens/legacy/VTokenInterfaceR1.sol +@@ -2,6 +2,7 @@ pragma solidity ^0.5.16; + + import "../../../Comptroller/ComptrollerInterface.sol"; + import "../../../InterestRateModels/InterestRateModel.sol"; ++import "../VTokenInterfaces.sol"; + + contract VTokenInterfaceR1 is VTokenStorage { + /** diff --git a/node_modules/@venusprotocol/venus-protocol/contracts/test/MockVBNB.sol b/node_modules/@venusprotocol/venus-protocol/contracts/test/MockVBNB.sol index adf9c4c..e8dcfb3 100644 --- a/node_modules/@venusprotocol/venus-protocol/contracts/test/MockVBNB.sol diff --git a/subgraphs/venus/config/index.ts b/subgraphs/venus/config/index.ts index 699bb863..c2a47f48 100644 --- a/subgraphs/venus/config/index.ts +++ b/subgraphs/venus/config/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env ts-node -import bscMainnetCoreDeployments from '@venusprotocol/venus-protocol/networks/mainnet.json'; -import bscTestnetCoreDeployments from '@venusprotocol/venus-protocol/networks/testnet.json'; +import bscMainnetCoreDeployments from '@venusprotocol/venus-protocol/deployments/bscmainnet_addresses.json'; +import bscTestnetCoreDeployments from '@venusprotocol/venus-protocol/deployments/bsctestnet_addresses.json'; import fs from 'fs'; import Mustache from 'mustache'; @@ -19,17 +19,17 @@ const main = () => { const config = { docker: { network: 'hardhat', - comptrollerAddress: '0x94d1820b2D1c7c7452A163983Dc888CEC546b77D', + comptrollerAddress: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9', startBlock: '0', }, chapel: { network: 'chapel', - comptrollerAddress: bscTestnetCoreDeployments.Contracts.Unitroller, + comptrollerAddress: bscTestnetCoreDeployments.addresses.Unitroller, startBlock: '2470000', }, bsc: { network: 'bsc', - comptrollerAddress: bscMainnetCoreDeployments.Contracts.Unitroller, + comptrollerAddress: bscMainnetCoreDeployments.addresses.Unitroller, startBlock: '2470000', }, }; diff --git a/subgraphs/venus/package.json b/subgraphs/venus/package.json index 3174eea6..acab5568 100644 --- a/subgraphs/venus/package.json +++ b/subgraphs/venus/package.json @@ -22,12 +22,13 @@ "prepare:chapel": "NETWORK=chapel yarn ts-node config/index.ts", "prepare:bsc": "NETWORK=bsc yarn ts-node config/index.ts", "test": "graph test", - "test:integration": "true" + "generate-subgraph-types": "rm -rf /subgraph-client/.graphclient && yarn graphclient build --dir ./subgraph-client", + "test:integration": "yarn hardhat test tests/integration/index.ts --network localhost" }, "dependencies": { "@graphprotocol/client-cli": "3.0.0", "@graphprotocol/graph-cli": "^0.73.0", - "@venusprotocol/venus-protocol": "5.2.0", + "@venusprotocol/venus-protocol": "9.0.0-dev.6", "@venusprotocol/venus-protocol-orig-events": "npm:@venusprotocol/venus-protocol@2.2.1", "hardhat": "^2.10.2", "ts-node": "^10.9.2" diff --git a/subgraphs/venus/schema.graphql b/subgraphs/venus/schema.graphql index 5216708f..e2c1aea7 100644 --- a/subgraphs/venus/schema.graphql +++ b/subgraphs/venus/schema.graphql @@ -59,8 +59,10 @@ type Market @entity { borrowIndexMantissa: BigInt! "The factor determining interest that goes to reserves" reserveFactor: BigInt! - "Underlying token price in USD cents" - underlyingPriceCents: BigInt! + "Last recorded Underlying token price in USD cents" + lastUnderlyingPriceCents: BigInt! + "Block price was last updated" + lastUnderlyingPriceBlockNumber: BigInt! "Underlying token decimal length" underlyingDecimals: Int! "Total XVS Distributed for this market" @@ -106,22 +108,15 @@ type AccountVToken @entity { market: Market! "Relation to user" account: Account! - "Block number this asset was updated at in the contract" - accrualBlockNumber: BigInt! "True if user is entered, false if they are exited" enteredMarket: Boolean! - "VToken balance of the user" vTokenBalanceMantissa: BigInt! "Total amount of underlying redeemed" totalUnderlyingRedeemedMantissa: BigInt! - "The value of the borrow index upon users last interaction" - accountBorrowIndexMantissa: BigInt! - "Total amount underlying borrowed, exclusive of interest" - totalUnderlyingBorrowedMantissa: BigInt! "Total amount underlying repaid" totalUnderlyingRepaidMantissa: BigInt! - "Current borrow balance stored in contract (exclusive of interest since accrualBlockNumber)" + "Stored borrow balance stored in contract (exclusive of interest since accrualBlockNumber)" storedBorrowBalanceMantissa: BigInt! } diff --git a/subgraphs/venus/src/mappings/comptroller.ts b/subgraphs/venus/src/mappings/comptroller.ts index 0dca4cf1..7af2ffc3 100644 --- a/subgraphs/venus/src/mappings/comptroller.ts +++ b/subgraphs/venus/src/mappings/comptroller.ts @@ -26,11 +26,8 @@ export function handleMarketListed(event: MarketListed): void { export function handleMarketEntered(event: MarketEntered): void { const market = getOrCreateMarket(event.params.vToken, event); getOrCreateAccount(event.params.account); - const accountVToken = getOrCreateAccountVToken( - Address.fromBytes(market.id), - event.params.account, - event, - ); + const result = getOrCreateAccountVToken(Address.fromBytes(market.id), event.params.account); + const accountVToken = result.entity; accountVToken.enteredMarket = true; accountVToken.save(); } @@ -38,11 +35,8 @@ export function handleMarketEntered(event: MarketEntered): void { export function handleMarketExited(event: MarketExited): void { const market = getOrCreateMarket(event.params.vToken, event); getOrCreateAccount(event.params.account); - const accountVToken = getOrCreateAccountVToken( - Address.fromBytes(market.id), - event.params.account, - event, - ); + const result = getOrCreateAccountVToken(Address.fromBytes(market.id), event.params.account); + const accountVToken = result.entity; accountVToken.enteredMarket = false; accountVToken.save(); } diff --git a/subgraphs/venus/src/mappings/vToken.ts b/subgraphs/venus/src/mappings/vToken.ts index 291012c4..06cade7d 100644 --- a/subgraphs/venus/src/mappings/vToken.ts +++ b/subgraphs/venus/src/mappings/vToken.ts @@ -51,11 +51,15 @@ import { getUnderlyingPrice } from '../utilities'; * Mints originate from the vToken address, not 0x000000, which is typical of ERC-20s */ export function handleMint(event: Mint): void { + if (event.params.mintAmount.equals(zeroBigInt32)) { + return; + } const marketAddress = event.address; const market = getOrCreateMarket(marketAddress, event); const vTokenContract = VToken.bind(marketAddress); if (event.params.mintTokens.equals(event.params.totalSupply)) { market.supplierCount = market.supplierCount.plus(oneBigInt); + getOrCreateAccount(event.params.minter); } // we'll first update the cash value of the market updateMarketCashMantissa(market, vTokenContract); @@ -67,20 +71,25 @@ export function handleMint(event: Mint): void { createMintEvent(event); - const account = getOrCreateAccount(event.params.minter); - account.save(); - - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.minter, event); - accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + const result = getOrCreateAccountVToken(marketAddress, event.params.minter); + const accountVToken = result.entity; + // Creation updates balance + if (!result.created) { + accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + } accountVToken.save(); } export function handleMintBehalf(event: MintBehalf): void { + if (event.params.mintAmount.equals(zeroBigInt32)) { + return; + } const marketAddress = event.address; const market = getOrCreateMarket(marketAddress, event); const vTokenContract = VToken.bind(marketAddress); if (event.params.mintTokens.equals(event.params.totalSupply)) { market.supplierCount = market.supplierCount.plus(oneBigInt); + getOrCreateAccount(event.params.receiver); } // we'll first update the cash value of the market updateMarketCashMantissa(market, vTokenContract); @@ -91,11 +100,12 @@ export function handleMintBehalf(event: MintBehalf): void { createMintBehalfEvent(event); - const account = getOrCreateAccount(event.params.receiver); - account.save(); - - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.receiver, event); - accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + const result = getOrCreateAccountVToken(marketAddress, event.params.receiver); + const accountVToken = result.entity; + // Creation updates balance + if (!result.created) { + accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + } accountVToken.save(); } @@ -110,6 +120,9 @@ export function handleMintBehalf(event: MintBehalf): void { * Transfer event will always get emitted with this */ export function handleRedeem(event: Redeem): void { + if (event.params.redeemAmount.equals(zeroBigInt32)) { + return; + } const marketAddress = event.address; const market = getOrCreateMarket(marketAddress, event); const vTokenContract = VToken.bind(marketAddress); @@ -129,10 +142,12 @@ export function handleRedeem(event: Redeem): void { createRedeemEvent(event); - getOrCreateAccount(event.params.redeemer); - - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.redeemer, event); - accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + const result = getOrCreateAccountVToken(marketAddress, event.params.redeemer); + const accountVToken = result.entity; + // Creation updates balance + if (!result.created) { + accountVToken.vTokenBalanceMantissa = event.params.totalSupply; + } accountVToken.totalUnderlyingRedeemedMantissa = accountVToken.totalUnderlyingRedeemedMantissa.plus(event.params.redeemAmount); accountVToken.save(); @@ -147,10 +162,13 @@ export function handleRedeem(event: Redeem): void { * Notes */ export function handleBorrow(event: Borrow): void { + if (event.params.borrowAmount.equals(zeroBigInt32)) { + return; + } const marketAddress = event.address; const market = getOrCreateMarket(marketAddress, event); const vTokenContract = VToken.bind(marketAddress); - if (event.params.accountBorrows == event.params.borrowAmount) { + if (event.params.accountBorrows.equals(event.params.borrowAmount)) { // if both the accountBorrows and the borrowAmount are the same, it means the account is a new borrower market.borrowerCount = market.borrowerCount.plus(oneBigInt); market.borrowerCountAdjusted = market.borrowerCountAdjusted.plus(oneBigInt); @@ -165,10 +183,9 @@ export function handleBorrow(event: Borrow): void { account.hasBorrowed = true; account.save(); - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.borrower, event); + const result = getOrCreateAccountVToken(marketAddress, event.params.borrower); + const accountVToken = result.entity; accountVToken.storedBorrowBalanceMantissa = event.params.accountBorrows; - accountVToken.accountBorrowIndexMantissa = market.borrowIndexMantissa; - accountVToken.totalUnderlyingBorrowedMantissa = event.params.totalBorrows; accountVToken.save(); createBorrowEvent(event); @@ -188,9 +205,14 @@ export function handleBorrow(event: Borrow): void { * repay. */ export function handleRepayBorrow(event: RepayBorrow): void { + if (event.params.repayAmount.equals(zeroBigInt32)) { + return; + } + const marketAddress = event.address; const market = getOrCreateMarket(marketAddress, event); const vTokenContract = VToken.bind(marketAddress); + if (event.params.accountBorrows.equals(zeroBigInt32)) { market.borrowerCount = market.borrowerCount.minus(oneBigInt); market.borrowerCountAdjusted = market.borrowerCountAdjusted.minus(oneBigInt); @@ -206,11 +228,9 @@ export function handleRepayBorrow(event: RepayBorrow): void { market.save(); - getOrCreateAccount(event.params.borrower); - - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.borrower, event); + const result = getOrCreateAccountVToken(marketAddress, event.params.borrower); + const accountVToken = result.entity; accountVToken.storedBorrowBalanceMantissa = event.params.accountBorrows; - accountVToken.accountBorrowIndexMantissa = market.borrowIndexMantissa; accountVToken.totalUnderlyingRepaidMantissa = accountVToken.totalUnderlyingRepaidMantissa.plus( event.params.repayAmount, ); @@ -253,9 +273,11 @@ export function handleLiquidateBorrow(event: LiquidateBorrow): void { event.params.seizeTokens, ); - const accountVToken = getOrCreateAccountVToken(event.address, event.params.borrower, event); - accountVToken.storedBorrowBalanceMantissa = accountVToken.storedBorrowBalanceMantissa.minus( - event.params.repayAmount, + const vTokenContract = VToken.bind(event.address); + const result = getOrCreateAccountVToken(event.address, event.params.borrower); + const accountVToken = result.entity; + accountVToken.storedBorrowBalanceMantissa = vTokenContract.borrowBalanceCurrent( + event.params.borrower, ); accountVToken.save(); } @@ -273,31 +295,37 @@ export function handleLiquidateBorrow(event: LiquidateBorrow): void { export function handleTransfer(event: Transfer): void { // We only updateMarket() if accrual block number is not up to date. This will only happen // with normal transfers, since mint, redeem, and seize transfers will already run updateMarket() - let market = getOrCreateMarket(event.address, event); - let accountFromAddress = event.params.from; let accountToAddress = event.params.to; // Checking if the event is FROM the vToken contract or null (i.e. this will not run when minting) // Checking if the event is TO the vToken contract (i.e. this will not run when redeeming) // @TODO Edge case where someone who accidentally sends vTokens to a vToken contract, where it will not get recorded. if ( - accountFromAddress != market.id && - accountFromAddress != nullAddress && - accountToAddress != market.id + accountFromAddress.notEqual(event.address) && + accountFromAddress.notEqual(nullAddress) && + accountToAddress.notEqual(event.address) ) { getOrCreateAccount(accountFromAddress); - const accountFromVToken = getOrCreateAccountVToken(event.address, accountFromAddress, event); - accountFromVToken.vTokenBalanceMantissa = accountFromVToken.vTokenBalanceMantissa.minus( - event.params.amount, - ); + const resultFrom = getOrCreateAccountVToken(event.address, accountFromAddress); + const accountFromVToken = resultFrom.entity; + + // Creation updates balance + if (!resultFrom.created) { + accountFromVToken.vTokenBalanceMantissa = accountFromVToken.vTokenBalanceMantissa.minus( + event.params.amount, + ); + } accountFromVToken.save(); - getOrCreateAccount(accountToAddress); - const accountToVToken = getOrCreateAccountVToken(event.address, accountToAddress, event); - accountToVToken.vTokenBalanceMantissa = accountToVToken.vTokenBalanceMantissa.plus( - event.params.amount, - ); - accountToVToken.save(); + const resultTo = getOrCreateAccountVToken(event.address, accountToAddress); + const accountToVToken = resultTo.entity; + // Creation updates balance + if (!resultTo.created) { + accountToVToken.vTokenBalanceMantissa = accountToVToken.vTokenBalanceMantissa.plus( + event.params.amount, + ); + accountToVToken.save(); + } } createTransferEvent(event); } @@ -312,16 +340,20 @@ export function handleAccrueInterest(event: AccrueInterest): void { market.blockTimestamp = event.block.timestamp.toI32(); market.borrowIndexMantissa = event.params.borrowIndex; market.totalBorrowsMantissa = event.params.totalBorrows; - market.cashMantissa = event.params.cashPrior; - market.underlyingPriceCents = + updateMarketCashMantissa(market, vTokenContract); + market.lastUnderlyingPriceCents = market.symbol == 'vBNB' ? zeroBigInt32 : getUnderlyingPrice(marketAddress, market.underlyingDecimals); + market.lastUnderlyingPriceBlockNumber = event.block.number; // the AccrueInterest event updates the reserves but does not report the new value in the params - market.reservesMantissa = vTokenContract.totalReserves(); - // since the total reserves likely changed, we'll also update the market rates - updateMarketRates(market, vTokenContract); + const totalReserves = vTokenContract.totalReserves(); + if (totalReserves.notEqual(market.reservesMantissa)) { + market.reservesMantissa = totalReserves; + // since the total reserves changed, we'll also update the market rates + updateMarketRates(market, vTokenContract); + } market.save(); } @@ -344,10 +376,17 @@ function getVTokenBalance(vTokenAddress: Address, accountAddress: Address): BigI } export function handleMintV1(event: MintV1): void { + if (event.params.mintAmount.equals(zeroBigInt32)) { + return; + } + const marketAddress = event.address; const market = getOrCreateMarket(event.address, event); const vTokenContract = VToken.bind(marketAddress); - if (event.params.mintTokens.equals(getVTokenBalance(event.address, event.params.minter))) { + const result = getOrCreateAccountVToken(marketAddress, event.params.minter); + const accountVToken = result.entity; + + if (event.params.mintTokens.equals(accountVToken.vTokenBalanceMantissa)) { market.supplierCount = market.supplierCount.plus(oneBigInt); } // we'll first update the cash value of the market and then the rates, since they depend on it @@ -360,18 +399,27 @@ export function handleMintV1(event: MintV1): void { createMintEvent(event); - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.minter, event); - accountVToken.vTokenBalanceMantissa = accountVToken.vTokenBalanceMantissa.plus( - event.params.mintTokens, - ); + getOrCreateAccount(event.params.minter); + + // Creation updates balance + if (!result.created) { + const accountVTokenBalance = accountVToken.vTokenBalanceMantissa.plus(event.params.mintTokens); + accountVToken.vTokenBalanceMantissa = accountVTokenBalance; + } accountVToken.save(); } export function handleMintBehalfV1(event: MintBehalfV1): void { + if (event.params.mintAmount.equals(zeroBigInt32)) { + return; + } const marketAddress = event.address; const market = getOrCreateMarket(event.address, event); const vTokenContract = VToken.bind(marketAddress); - if (event.params.mintTokens.equals(getVTokenBalance(event.address, event.params.receiver))) { + const result = getOrCreateAccountVToken(marketAddress, event.params.receiver); + const accountVToken = result.entity; + + if (event.params.mintTokens.equals(accountVToken.vTokenBalanceMantissa)) { market.supplierCount = market.supplierCount.plus(oneBigInt); } // we'll first update the cash value of the market @@ -384,10 +432,13 @@ export function handleMintBehalfV1(event: MintBehalfV1): void { createMintBehalfEvent(event); - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.receiver, event); - accountVToken.vTokenBalanceMantissa = accountVToken.vTokenBalanceMantissa.plus( - event.params.mintTokens, - ); + getOrCreateAccount(event.params.receiver); + + // Creation updates balance + if (!result.created) { + const accountVTokenBalance = accountVToken.vTokenBalanceMantissa.plus(event.params.mintTokens); + accountVToken.vTokenBalanceMantissa = accountVTokenBalance; + } accountVToken.save(); } @@ -406,21 +457,27 @@ export function handleRedeemV1(event: RedeemV1): void { market.totalSupplyVTokenMantissa = market.totalSupplyVTokenMantissa.minus( event.params.redeemTokens, ); - market.save(); + createRedeemEvent(event); - const accountVToken = getOrCreateAccountVToken(marketAddress, event.params.redeemer, event); - accountVToken.vTokenBalanceMantissa = accountVToken.vTokenBalanceMantissa.minus( - event.params.redeemTokens, - ); + const result = getOrCreateAccountVToken(marketAddress, event.params.redeemer); + const accountVToken = result.entity; + // Creation updates balance + if (!result.created) { + accountVToken.vTokenBalanceMantissa = accountVToken.vTokenBalanceMantissa.minus( + event.params.redeemTokens, + ); + } + accountVToken.totalUnderlyingRedeemedMantissa = + accountVToken.totalUnderlyingRedeemedMantissa.plus(event.params.redeemAmount); + accountVToken.save(); } export function handleReservesAdded(event: ReservesAdded): void { const marketAddress = event.address; const market = getOrCreateMarket(event.address, event); const vTokenContract = VToken.bind(marketAddress); - market.reservesMantissa = event.params.newTotalReserves; updateMarketRates(market, vTokenContract); diff --git a/subgraphs/venus/src/operations/getOrCreate.ts b/subgraphs/venus/src/operations/getOrCreate.ts index d4adc21b..9c8c6cd2 100644 --- a/subgraphs/venus/src/operations/getOrCreate.ts +++ b/subgraphs/venus/src/operations/getOrCreate.ts @@ -70,10 +70,11 @@ export function getOrCreateMarket(marketAddress: Address, event: ethereum.Event) vTokenContract.try_reserveFactorMantissa(), 'vBEP20 try_reserveFactorMantissa()', ); - market.underlyingPriceCents = + market.lastUnderlyingPriceCents = market.symbol == 'vBNB' ? zeroBigInt32 : getUnderlyingPrice(marketAddress, market.underlyingDecimals); + market.lastUnderlyingPriceBlockNumber = event.block.number; market.accrualBlockNumber = vTokenContract.accrualBlockNumber().toI32(); market.totalXvsDistributedMantissa = zeroBigInt32; @@ -111,29 +112,32 @@ export function getOrCreateAccount(accountId: Bytes): Account { return account; } +export class GetOrCreateAccountVTokenReturn { + entity: AccountVToken; + created: boolean; +} + export function getOrCreateAccountVToken( marketId: Address, accountId: Address, - event: ethereum.Event, -): AccountVToken { +): GetOrCreateAccountVTokenReturn { const accountVTokenId = getAccountVTokenId(marketId, accountId); let accountVToken = AccountVToken.load(accountVTokenId); + let created = false; if (!accountVToken) { + created = true; accountVToken = new AccountVToken(accountVTokenId); accountVToken.market = marketId; accountVToken.account = accountId; - accountVToken.accrualBlockNumber = event.block.number; // we need to set an initial real onchain value to this otherwise it will never be accurate const vTokenContract = VToken.bind(marketId); accountVToken.vTokenBalanceMantissa = vTokenContract.balanceOf(accountId); accountVToken.totalUnderlyingRedeemedMantissa = zeroBigInt32; - accountVToken.accountBorrowIndexMantissa = zeroBigInt32; - accountVToken.totalUnderlyingBorrowedMantissa = zeroBigInt32; accountVToken.totalUnderlyingRepaidMantissa = zeroBigInt32; accountVToken.storedBorrowBalanceMantissa = zeroBigInt32; accountVToken.enteredMarket = false; accountVToken.save(); } - return accountVToken; + return { entity: accountVToken, created }; } diff --git a/subgraphs/venus/subgraph-client/.graphclientrc.yml b/subgraphs/venus/subgraph-client/.graphclientrc.yml new file mode 100644 index 00000000..1d8b7a9c --- /dev/null +++ b/subgraphs/venus/subgraph-client/.graphclientrc.yml @@ -0,0 +1,8 @@ +sources: + - name: venus-subgraph + handler: + graphql: + endpoint: http://graph-node:8000/subgraphs/name/venusprotocol/venus-subgraph + +documents: + - '../**/*.graphql' diff --git a/subgraphs/venus/subgraph-client/index.ts b/subgraphs/venus/subgraph-client/index.ts new file mode 100644 index 00000000..5a12a364 --- /dev/null +++ b/subgraphs/venus/subgraph-client/index.ts @@ -0,0 +1,72 @@ +import { DocumentNode } from 'graphql'; +import { Client as UrqlClient, createClient } from 'urql/core'; + +import { + AccountByIdDocument, + AccountVTokenByAccountAndMarketQueryDocument, + AccountVTokenByAccountIdDocument, + AccountVTokensDocument, + MarketByIdDocument, + MarketsDocument, +} from './.graphclient'; + +class SubgraphClient { + urqlClient: UrqlClient; + + constructor(url: string) { + this.urqlClient = createClient({ + url, + requestPolicy: 'network-only', + }); + } + + async query(document: DocumentNode, args: Record) { + const result = await this.urqlClient.query(document, args).toPromise(); + if (result.error) { + console.error(result.error); + } + return result; + } + + async getMarkets() { + const result = await this.query(MarketsDocument, {}); + return result; + } + + async getMarketById(id: string) { + const result = await this.query(MarketByIdDocument, { id }); + return result; + } + + async getAccountById(id: string) { + const result = await this.query(AccountByIdDocument, { id }); + return result; + } + + async getAccountVTokens() { + const result = await this.query(AccountVTokensDocument, {}); + return result; + } + + async getAccountVTokensByAccountId(accountId: string) { + const result = await this.query(AccountVTokenByAccountIdDocument, { accountId }); + return result; + } + + async getAccountVTokenByAccountAndMarket({ + marketId, + accountId, + }: { + marketId: string; + accountId: string; + }) { + const result = await this.query(AccountVTokenByAccountAndMarketQueryDocument, { + id: `${marketId}${accountId.replace('0x', '')}`, + }); + return result; + } +} + +export default new SubgraphClient( + 'http://graph-node:8000/subgraphs/name/venusprotocol/venus-subgraph', +); diff --git a/subgraphs/venus/subgraph-client/queries/accountByIdQuery.graphql b/subgraphs/venus/subgraph-client/queries/accountByIdQuery.graphql new file mode 100644 index 00000000..df921e7b --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/accountByIdQuery.graphql @@ -0,0 +1,14 @@ +query AccountById($id: ID!) { + account(id: $id) { + id + tokens { + id + vTokenBalanceMantissa + totalUnderlyingRedeemedMantissa + storedBorrowBalanceMantissa + } + countLiquidated + countLiquidator + hasBorrowed + } +} diff --git a/subgraphs/venus/subgraph-client/queries/accountVTokenByAccount.graphql b/subgraphs/venus/subgraph-client/queries/accountVTokenByAccount.graphql new file mode 100644 index 00000000..649c5633 --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/accountVTokenByAccount.graphql @@ -0,0 +1,16 @@ +query AccountVTokenByAccountId($accountId: String!) { + accountVTokens(where: { account: $accountId }) { + id + market { + id + } + account { + id + } + enteredMarket + totalUnderlyingRedeemedMantissa + totalUnderlyingRepaidMantissa + } +} + + diff --git a/subgraphs/venus/subgraph-client/queries/accountVTokenByAccountAndMarket.graphql b/subgraphs/venus/subgraph-client/queries/accountVTokenByAccountAndMarket.graphql new file mode 100644 index 00000000..3fe94351 --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/accountVTokenByAccountAndMarket.graphql @@ -0,0 +1,17 @@ +query AccountVTokenByAccountAndMarketQuery($id: ID!) { + accountVToken(id: $id) { + id + market { + id + } + account { + id + hasBorrowed + } + enteredMarket + vTokenBalanceMantissa + totalUnderlyingRedeemedMantissa + totalUnderlyingRepaidMantissa + storedBorrowBalanceMantissa + } +} diff --git a/subgraphs/venus/subgraph-client/queries/accountVTokens.graphql b/subgraphs/venus/subgraph-client/queries/accountVTokens.graphql new file mode 100644 index 00000000..a94c6b16 --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/accountVTokens.graphql @@ -0,0 +1,14 @@ +query AccountVTokens { + accountVTokens { + id + market { + id + } + account { + id + } + enteredMarket + totalUnderlyingRedeemedMantissa + totalUnderlyingRepaidMantissa + } +} diff --git a/subgraphs/venus/subgraph-client/queries/accountsInAddressQuery.graphql b/subgraphs/venus/subgraph-client/queries/accountsInAddressQuery.graphql new file mode 100644 index 00000000..27d872bd --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/accountsInAddressQuery.graphql @@ -0,0 +1,11 @@ +query Accounts { + accounts { + id + tokens { + id + } + countLiquidated + countLiquidator + hasBorrowed + } +} diff --git a/subgraphs/venus/subgraph-client/queries/marketByIdQuery.graphql b/subgraphs/venus/subgraph-client/queries/marketByIdQuery.graphql new file mode 100644 index 00000000..66d2a06f --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/marketByIdQuery.graphql @@ -0,0 +1,28 @@ +query MarketById($id: ID!) { + market(id: $id) { + id + borrowRateMantissa + cashMantissa + collateralFactorMantissa + exchangeRateMantissa + interestRateModelAddress + name + reservesMantissa + supplyRateMantissa + symbol + underlyingAddress + underlyingName + lastUnderlyingPriceCents + lastUnderlyingPriceBlockNumber + underlyingSymbol + accrualBlockNumber + blockTimestamp + borrowIndexMantissa + totalSupplyVTokenMantissa + totalBorrowsMantissa + underlyingDecimals + supplierCount + borrowerCount + borrowerCountAdjusted + } +} diff --git a/subgraphs/venus/subgraph-client/queries/marketsQuery.graphql b/subgraphs/venus/subgraph-client/queries/marketsQuery.graphql new file mode 100644 index 00000000..8aacf229 --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/marketsQuery.graphql @@ -0,0 +1,26 @@ +query Markets { + markets { + id + borrowRateMantissa + cashMantissa + collateralFactorMantissa + exchangeRateMantissa + interestRateModelAddress + name + reservesMantissa + supplyRateMantissa + symbol + underlyingAddress + underlyingName + underlyingSymbol + accrualBlockNumber + blockTimestamp + borrowIndexMantissa + lastUnderlyingPriceCents + lastUnderlyingPriceBlockNumber + underlyingDecimals + supplierCount + borrowerCount + borrowerCountAdjusted + } +} diff --git a/subgraphs/venus/subgraph-client/queries/transactions.graphql b/subgraphs/venus/subgraph-client/queries/transactions.graphql new file mode 100644 index 00000000..6f9e5296 --- /dev/null +++ b/subgraphs/venus/subgraph-client/queries/transactions.graphql @@ -0,0 +1,11 @@ +query Transactions { + transactions { + id + type + from + amountMantissa + to + blockNumber + blockTime + } +} diff --git a/subgraphs/venus/tests/VToken/index.test.ts b/subgraphs/venus/tests/VToken/index.test.ts index 9244b9a7..1e3bc53c 100644 --- a/subgraphs/venus/tests/VToken/index.test.ts +++ b/subgraphs/venus/tests/VToken/index.test.ts @@ -160,13 +160,6 @@ describe('VToken', () => { handleRedeem(redeemEvent); - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accrualBlockNumber', - redeemEvent.block.number.toString(), - ); - assert.fieldEquals( 'AccountVToken', accountVTokenId, @@ -214,33 +207,11 @@ describe('VToken', () => { /** Fire Event */ handleBorrow(borrowEvent); - const accountVTokenId = getAccountVTokenId(aaaTokenAddress, borrower).toHexString(); const market = getMarket(aaaTokenAddress); assert.assertNotNull(market); if (!market) { return; } - - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accrualBlockNumber', - borrowEvent.block.number.toString(), - ); - - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'totalUnderlyingBorrowedMantissa', - totalBorrows.toString(), - ); - - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accountBorrowIndexMantissa', - market.borrowIndexMantissa.toString(), - ); }); test('registers repay borrow event', () => { @@ -285,20 +256,6 @@ describe('VToken', () => { return; } - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accrualBlockNumber', - repayBorrowEvent.block.number.toString(), - ); - - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accountBorrowIndexMantissa', - market.borrowIndexMantissa.toString(), - ); - assert.fieldEquals('AccountVToken', accountVTokenId, 'totalUnderlyingBorrowedMantissa', '0'); }); @@ -471,13 +428,6 @@ describe('VToken', () => { const accountVTokenId = getAccountVTokenId(aaaTokenAddress, to).toHexString(); /** AccountVToken */ - assert.fieldEquals( - 'AccountVToken', - accountVTokenId, - 'accrualBlockNumber', - transferEvent.block.number.toString(), - ); - assert.fieldEquals( 'AccountVToken', accountVTokenId, diff --git a/subgraphs/venus/tests/integration/constants.ts b/subgraphs/venus/tests/integration/constants.ts new file mode 100644 index 00000000..2af5fe51 --- /dev/null +++ b/subgraphs/venus/tests/integration/constants.ts @@ -0,0 +1,5 @@ +// Subgraph Name +export const SUBGRAPH_ACCOUNT = 'venusprotocol'; +export const SUBGRAPH_NAME = 'venus-subgraph'; + +export const SYNC_DELAY = 2000; diff --git a/subgraphs/venus/tests/integration/index.ts b/subgraphs/venus/tests/integration/index.ts new file mode 100644 index 00000000..ca9ebeeb --- /dev/null +++ b/subgraphs/venus/tests/integration/index.ts @@ -0,0 +1,2 @@ +import './setup'; +import './vTokens.ts'; diff --git a/subgraphs/venus/tests/integration/setup.ts b/subgraphs/venus/tests/integration/setup.ts new file mode 100644 index 00000000..8db7b906 --- /dev/null +++ b/subgraphs/venus/tests/integration/setup.ts @@ -0,0 +1,17 @@ +import { deploy } from 'venus-subgraph-utils'; + +import { SUBGRAPH_ACCOUNT, SUBGRAPH_NAME, SYNC_DELAY } from './constants'; + +describe('Deploy Subgraph', function () { + it('should deploy subgraph', async function () { + const root = `${__dirname}/../..`; + + await deploy({ + root, + packageName: 'venus-subgraph', + subgraphAccount: SUBGRAPH_ACCOUNT, + subgraphName: SUBGRAPH_NAME, + syncDelay: SYNC_DELAY, + }); + }); +}); diff --git a/subgraphs/venus/tests/integration/utils/deploy.ts b/subgraphs/venus/tests/integration/utils/deploy.ts new file mode 100644 index 00000000..ec7dbb97 --- /dev/null +++ b/subgraphs/venus/tests/integration/utils/deploy.ts @@ -0,0 +1,25 @@ +import { exec, fetchSubgraph, waitForSubgraphToBeSynced } from 'venus-subgraph-utils'; + +import { SUBGRAPH_ACCOUNT, SUBGRAPH_NAME, SYNC_DELAY } from '../constants'; + +const deploy = async () => { + const root = __dirname; + + // Create Subgraph Connection + const subgraph = fetchSubgraph(SUBGRAPH_ACCOUNT, SUBGRAPH_NAME); + + // Build and Deploy Subgraph + console.log('Build and deploy subgraph...'); + exec(`yarn workspace venus-subgraph run prepare:docker`, root); + exec(`yarn workspace venus-subgraph run codegen`, root); + exec(`yarn workspace venus-subgraph run build:docker`, root); + exec(`yarn workspace venus-subgraph run create:docker`, root); + + const deployCmd = `yarn graph deploy ${SUBGRAPH_ACCOUNT}/${SUBGRAPH_NAME} --ipfs http://ipfs:5001 --node http://graph-node:8020/ --version-label v${Date.now().toString()}`; + exec(deployCmd, root); + + await waitForSubgraphToBeSynced(SYNC_DELAY); + return { subgraph }; +}; + +export default deploy; diff --git a/subgraphs/venus/tests/integration/vTokens.ts b/subgraphs/venus/tests/integration/vTokens.ts new file mode 100644 index 00000000..b262a2ad --- /dev/null +++ b/subgraphs/venus/tests/integration/vTokens.ts @@ -0,0 +1,1145 @@ +import { JsonRpcSigner } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { Contract } from 'ethers'; +import hre, { ethers } from 'hardhat'; +import { waitForSubgraphToBeSynced } from 'venus-subgraph-utils'; + +import subgraphClient from '../../subgraph-client'; + +const { parseUnits, formatUnits } = ethers.utils; +const { MaxUint256 } = ethers.constants; + +const checkSupply = async (account: string, vToken: Contract) => { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: account, + }); + const accountContractBalance = await vToken.balanceOf(account); + + expect(accountVToken?.vTokenBalanceMantissa || '0').to.equal(accountContractBalance.toString()); + // on market + const accrualBlockNumber = await vToken.accrualBlockNumber(); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.accrualBlockNumber.toString()).to.equal(accrualBlockNumber.toString()); +}; + +const checkBorrows = async (account: string, vToken: Contract) => { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: account, + }); + const accountContractBalance = await vToken.borrowBalanceStored(account); + expect(accountVToken?.storedBorrowBalanceMantissa || '0').to.equal( + accountContractBalance.toString(), + ); + expect(accountVToken.account.hasBorrowed).to.equal(true); + // on market + const accrualBlockNumber = await vToken.accrualBlockNumber(); + const borrowIndex = await vToken.borrowIndex(); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.accrualBlockNumber.toString()).to.equal(accrualBlockNumber.toString()); + expect(market?.borrowIndexMantissa.toString()).to.equal(borrowIndex.toString()); +}; + +describe('VToken events', function () { + const syncDelay = 2000; + let rootSigner: JsonRpcSigner; + let supplier1Signer: JsonRpcSigner; + let supplier2Signer: JsonRpcSigner; + let supplier3Signer: JsonRpcSigner; + let borrower1Signer: JsonRpcSigner; + let borrower2Signer: JsonRpcSigner; + let borrower3Signer: JsonRpcSigner; + let liquidator1Signer: JsonRpcSigner; + let liquidator2Signer: JsonRpcSigner; + let liquidator3Signer: JsonRpcSigner; + let suppliers: JsonRpcSigner[]; + let borrowers: JsonRpcSigner[]; + let comptroller: Contract; + // R1 Interface + let vUsdcToken: Contract; + let vWBnbToken: Contract; + let vEthToken: Contract; + // Current Interface + let vDogeToken: Contract; + let vFdusdToken: Contract; + let vUsdtToken: Contract; + let oracle: Contract; + + before(async function () { + const { + deployer: root, + supplier1, + supplier2, + supplier3, + borrower1, + borrower2, + borrower3, + liquidator1, + liquidator2, + liquidator3, + } = await hre.getNamedAccounts(); + + rootSigner = ethers.provider.getSigner(root); + supplier1Signer = ethers.provider.getSigner(supplier1); + supplier2Signer = ethers.provider.getSigner(supplier2); + supplier3Signer = ethers.provider.getSigner(supplier3); + + borrower1Signer = ethers.provider.getSigner(borrower1); + borrower2Signer = ethers.provider.getSigner(borrower2); + borrower3Signer = ethers.provider.getSigner(borrower3); + + liquidator1Signer = ethers.provider.getSigner(liquidator1); + liquidator2Signer = ethers.provider.getSigner(liquidator2); + liquidator3Signer = ethers.provider.getSigner(liquidator3); + + suppliers = [supplier1Signer, supplier2Signer, supplier3Signer]; + borrowers = [borrower1Signer, borrower2Signer, borrower3Signer]; + + const fundAccounts = async (token: Contract, amount: string) => { + const underlying = await token.underlying(); + const underlyingContract = await ethers.getContractAt('BEP20Harness', underlying); + await Promise.all( + [ + supplier1Signer, + supplier2Signer, + supplier3Signer, + borrower1Signer, + borrower2Signer, + borrower3Signer, + liquidator1Signer, + liquidator2Signer, + liquidator3Signer, + ].map(async account => { + await underlyingContract.connect(account).faucet(amount); + await underlyingContract.connect(account).approve(token.address, MaxUint256); + await underlyingContract.connect(rootSigner).faucet(amount); + await underlyingContract.connect(rootSigner).approve(token.address, MaxUint256); + }), + ); + }; + + comptroller = await ethers.getContract('Unitroller'); + // Original Tokens + vUsdcToken = await ethers.getContract('vUSDC'); + vWBnbToken = await ethers.getContract('vBNB'); + vEthToken = await ethers.getContract('vETH'); + + vDogeToken = await ethers.getContract('vDOGE'); + vFdusdToken = await ethers.getContract('vFDUSD'); + vUsdtToken = await ethers.getContract('vUSDT'); + + // Set Prices + oracle = await ethers.getContract('MockPriceOracleUnderlyingPrice'); + // USDC $1 + await oracle.setPrice(vUsdcToken.address, parseUnits('1', 18).toString()); + // BNB $500 + await oracle.setPrice(vWBnbToken.address, parseUnits('500', 18).toString()); + // ETH $5000 + await oracle.setPrice(vEthToken.address, parseUnits('5000', 18).toString()); + + // USDC $1 + await oracle.setPrice(vFdusdToken.address, parseUnits('1', 18).toString()); + // DOGE $.5 + await oracle.setPrice(vDogeToken.address, parseUnits('.5', 18).toString()); + // USDT $1 + await oracle.setPrice(vUsdtToken.address, parseUnits('1', 18).toString()); + + await comptroller._setMarketSupplyCaps( + [ + vUsdcToken.address, + vWBnbToken.address, + vEthToken.address, + vFdusdToken.address, + vUsdtToken.address, + vDogeToken.address, + ], + [ + parseUnits('500000'), + parseUnits('500'), + parseUnits('500'), + parseUnits('500000'), + parseUnits('500000'), + parseUnits('5000000000000'), + ], + ); + await comptroller._setMarketBorrowCaps( + [ + vUsdcToken.address, + vWBnbToken.address, + vEthToken.address, + vFdusdToken.address, + vUsdtToken.address, + vDogeToken.address, + ], + [ + parseUnits('500000'), + parseUnits('500'), + parseUnits('500'), + parseUnits('500000'), + parseUnits('500000'), + parseUnits('5000000000000'), + ], + ); + + await comptroller._setCollateralFactor(vFdusdToken.address, parseUnits('0.9')); + await comptroller._setCollateralFactor(vUsdtToken.address, parseUnits('0.9')); + await comptroller._setCollateralFactor(vDogeToken.address, parseUnits('0.9')); + + await Promise.all( + ( + [ + [vUsdcToken, parseUnits('100000', 18).toString()], + [vWBnbToken, parseUnits('100000', 18).toString()], + [vEthToken, parseUnits('100000', 18).toString()], + [vFdusdToken, parseUnits('100000', 18).toString()], + [vDogeToken, parseUnits('1000000000', 18).toString()], + [vUsdtToken, parseUnits('100000', 18).toString()], + ] as [Contract, string][] + ).map(async data => { + await fundAccounts(data[0], data[1]); + }), + ); + + await waitForSubgraphToBeSynced(syncDelay); + }); + + describe('Original VToken events', () => { + it('should update the supplier count on the market when new minting', async function () { + // Supply all accounts + const fdusd1000Usd = await oracle.getAssetTokenAmount( + vFdusdToken.address, + parseUnits('1000', 36), + ); + const usdt2000Usd = await oracle.getAssetTokenAmount( + vUsdtToken.address, + parseUnits('2000', 36), + ); + const doge1000Usd = await oracle.getAssetTokenAmount( + vDogeToken.address, + parseUnits('1000', 36), + ); + + for (const account of suppliers) { + await vDogeToken.connect(account).mint(doge1000Usd.toString()); + await vFdusdToken.connect(account).mint(fdusd1000Usd.toString()); + await vUsdtToken.connect(account).mint(usdt2000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of suppliers) { + for (const vToken of [vFdusdToken, vUsdtToken, vDogeToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('3'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + } + } + + for (const account of borrowers) { + await vFdusdToken.connect(rootSigner).mintBehalf(account._address, fdusd1000Usd.toString()); + await vDogeToken.connect(rootSigner).mintBehalf(account._address, doge1000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of borrowers) { + for (const vToken of [vFdusdToken, vDogeToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + + expect(market?.supplierCount).to.equal('6'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + } + } + }); + + it('should not update the supplier count on the market when current accounts mint again', async function () { + // Supply all accounts + const fdusd100Usd = await oracle.getAssetTokenAmount( + vFdusdToken.address, + parseUnits('100', 36), + ); + const usdt100Usd = await oracle.getAssetTokenAmount( + vUsdtToken.address, + parseUnits('100', 36), + ); + const doge500Usd = await oracle.getAssetTokenAmount( + vDogeToken.address, + parseUnits('500', 36), + ); + + for (const account of suppliers) { + await vFdusdToken.connect(account).mint(fdusd100Usd.toString()); + await vUsdtToken.connect(account).mint(usdt100Usd.toString()); + await vDogeToken.connect(account).mint(doge500Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of suppliers) { + for (const vToken of [vFdusdToken, vDogeToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + } + await checkSupply(account._address, vUsdtToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vUsdtToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('3'); + } + + for (const account of borrowers) { + await vFdusdToken.connect(rootSigner).mintBehalf(account._address, fdusd100Usd.toString()); + await vDogeToken.connect(rootSigner).mintBehalf(account._address, doge500Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of borrowers) { + for (const vToken of [vFdusdToken, vDogeToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + } + } + }); + + it('should not update the supplier count on the market when partially redeeming', async function () { + for (const vToken of [vFdusdToken, vDogeToken]) { + const redeemAmount = (await vToken.balanceOf(supplier2Signer._address)).div(2); + + const exchangeRate = await vToken.callStatic.exchangeRateCurrent(); + + await vToken.connect(supplier2Signer).redeem(redeemAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkSupply(supplier2Signer._address, vToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: supplier2Signer._address, + }); + expect(accountVToken.totalUnderlyingRedeemedMantissa).to.equal( + formatUnits(redeemAmount.mul(exchangeRate), 18).split('.')[0], + ); // remove decimals + } + }); + + it('should update the supplier count on the market when fully redeemed', async function () { + for (const vToken of [vFdusdToken, vDogeToken]) { + const redeemAmount = await vToken.balanceOf(supplier2Signer._address); + await vToken.connect(supplier2Signer).redeem(redeemAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkSupply(supplier2Signer._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('5'); + } + }); + + it('should handle enter market', async function () { + await comptroller + .connect(borrower1Signer) + .enterMarkets([vFdusdToken.address, vUsdtToken.address, vDogeToken.address]); + await comptroller + .connect(borrower2Signer) + .enterMarkets([vFdusdToken.address, vUsdtToken.address, vDogeToken.address]); + await comptroller + .connect(borrower3Signer) + .enterMarkets([vFdusdToken.address, vUsdtToken.address, vDogeToken.address]); + + await waitForSubgraphToBeSynced(syncDelay); + + for (const vTokenAddress of [vFdusdToken.address, vUsdtToken.address, vDogeToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + + for (const vTokenAddress of [vFdusdToken.address, vUsdtToken.address, vDogeToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + + for (const vTokenAddress of [vFdusdToken.address, vUsdtToken.address, vDogeToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower3Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + }); + + it('should handle exit market', async function () { + await comptroller.connect(borrower1Signer).exitMarket(vUsdtToken.address); + await comptroller.connect(borrower2Signer).exitMarket(vDogeToken.address); + await comptroller.connect(borrower3Signer).exitMarket(vFdusdToken.address); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken: accountVTokenVUsdt }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vUsdtToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVTokenVUsdt?.enteredMarket).to.equal(false); + + const { + data: { accountVToken: accountVTokenVDoge }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vDogeToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVTokenVDoge?.enteredMarket).to.equal(false); + + const { + data: { accountVToken: accountVTokenVFusd }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vFdusdToken.address.toLowerCase(), + accountId: borrower3Signer._address, + }); + expect(accountVTokenVFusd?.enteredMarket).to.equal(false); + }); + + it('should update the borrower count on the market for new borrows', async function () { + const doge1250Usd = await oracle.getAssetTokenAmount( + vDogeToken.address, + parseUnits('1250', 36), + ); + const usdt50Usd = await oracle.getAssetTokenAmount(vUsdtToken.address, parseUnits('50', 36)); + const fdusd50Usd = await oracle.getAssetTokenAmount( + vFdusdToken.address, + parseUnits('50', 36), + ); + await vUsdtToken.connect(borrower1Signer).borrow(usdt50Usd); + await vDogeToken.connect(borrower2Signer).borrow(doge1250Usd); + await vFdusdToken.connect(borrower3Signer).borrow(fdusd50Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower1Signer._address, vDogeToken); + await checkBorrows(borrower1Signer._address, vUsdtToken); + await checkBorrows(borrower1Signer._address, vFdusdToken); + + for (const vToken of [vFdusdToken, vUsdtToken, vDogeToken]) { + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vToken.totalBorrows()).toString()); + } + }); + + it('should not update the borrower count on the market for repeated borrows', async function () { + const doge1070Usd = await oracle.getAssetTokenAmount( + vDogeToken.address, + parseUnits('1070', 36), + ); + + await vDogeToken.connect(borrower2Signer).borrow(doge1070Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower2Signer._address, vDogeToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vDogeToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vDogeToken.totalBorrows()).toString()); + }); + + it('should not update the borrower count on the market for partial repayment of borrow', async function () { + const doge20Usd = await oracle.getAssetTokenAmount(vDogeToken.address, parseUnits('20', 36)); + await vDogeToken.connect(borrower2Signer).repayBorrow(doge20Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower2Signer._address, vDogeToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vDogeToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vDogeToken.totalBorrows()).toString()); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vDogeToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVToken.totalUnderlyingRepaidMantissa).to.equal(doge20Usd.toString()); + }); + + it('should handle accrue interest event', async function () { + for (const vToken of [vFdusdToken, vDogeToken]) { + const prevAccrualBlockNumber = await vToken.accrualBlockNumber(); + const prevBorrowIndex = await vToken.borrowIndex(); + const prevTotalBorrows = await vToken.totalBorrows(); + const prevCash = await vToken.getCash(); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + await vToken.accrueInterest(); + + await waitForSubgraphToBeSynced(syncDelay); + expect(market.accrualBlockNumber).to.be.greaterThanOrEqual(prevAccrualBlockNumber); + expect(market.borrowIndexMantissa).to.be.greaterThanOrEqual(prevBorrowIndex); + expect(market.totalBorrowsMantissa).to.greaterThanOrEqual(prevTotalBorrows); + expect(market.cashMantissa).to.lessThanOrEqual(prevCash); + } + }); + + it('should handle accrue interest event with added reserves', async function () { + const fdusd50Usd = await oracle.getAssetTokenAmount( + vFdusdToken.address, + parseUnits('10', 36), + ); + const doge50Usd = await oracle.getAssetTokenAmount(vDogeToken.address, parseUnits('50', 36)); + for (const [vToken, amount] of [ + [vFdusdToken, fdusd50Usd], + [vDogeToken, doge50Usd], + ]) { + const borrowRateMantissaPrev = await vToken.borrowRatePerBlock(); + const exchangeRateMantissaPrev = await vToken.callStatic.exchangeRateCurrent(); + const supplyRateMantissaPrev = await vToken.supplyRatePerBlock(); + + await vToken._addReserves(amount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + + expect(market.reservesMantissa).to.equal(amount); + expect(market.borrowRateMantissa).to.be.greaterThanOrEqual(borrowRateMantissaPrev); + expect(market.exchangeRateMantissa).to.be.greaterThanOrEqual(exchangeRateMantissaPrev); + expect(market.supplyRateMantissa).to.be.greaterThanOrEqual(supplyRateMantissaPrev); + } + }); + + it('should handle liquidateBorrow event', async function () { + await oracle.setPrice(vDogeToken.address, parseUnits('500', 18).toString()); + await oracle.setPrice(vFdusdToken.address, parseUnits('0.9', 18).toString()); + + const doge10Usd = await oracle.getAssetTokenAmount(vDogeToken.address, parseUnits('10', 36)); + + await vDogeToken + .connect(liquidator1Signer) + .liquidateBorrow(borrower2Signer._address, doge10Usd.toString(), vFdusdToken.address); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vDogeToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + + expect(accountVToken.storedBorrowBalanceMantissa).to.be.approximately( + ethers.BigNumber.from( + await vDogeToken.callStatic.borrowBalanceCurrent(borrower2Signer._address), + ), + 1e11, + ); + + const { + data: { account: accountBorrower }, + } = await subgraphClient.getAccountById(borrower2Signer._address); + expect(accountBorrower.countLiquidated).to.equal(1); + + const { + data: { account: accountLiquidator }, + } = await subgraphClient.getAccountById(liquidator1Signer._address); + expect(accountLiquidator.countLiquidator).to.equal(1); + + // Reset prices + await oracle.setPrice(vFdusdToken.address, parseUnits('1', 18).toString()); + await oracle.setPrice(vDogeToken.address, parseUnits('.5', 18).toString()); + }); + + it('should update the borrower count on the market for full repayment of borrow', async function () { + const { + data: { accountVToken: accountVTokenPrev }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vDogeToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + const borrowBalance = await vDogeToken.callStatic.borrowBalanceCurrent( + borrower2Signer._address, + ); + + const repayAmount = borrowBalance.add(9225884015468); + await vDogeToken.connect(borrower2Signer).repayBorrow(repayAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower2Signer._address, vDogeToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vDogeToken.address.toLowerCase()); + expect(await vDogeToken.callStatic.borrowBalanceCurrent(borrower2Signer._address)).to.equal( + 0, + ); + expect(market?.borrowerCountAdjusted).to.equal('0'); + expect(market?.borrowerCount).to.equal('0'); + expect(market?.totalBorrowsMantissa).to.equal((await vDogeToken.totalBorrows()).toString()); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vDogeToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVToken.totalUnderlyingRepaidMantissa).to.be.approximately( + ethers.BigNumber.from(accountVTokenPrev.totalUnderlyingRepaidMantissa).add(repayAmount), + 1e10, + ); + }); + + it('should handle transfer event', async function () { + for (const [supplier, vToken] of [ + [supplier1Signer, vFdusdToken], + [supplier2Signer, vUsdtToken], + [supplier3Signer, vDogeToken], + ]) { + const supplierBalance = (await vToken.balanceOf(supplier._address)).div(2); + const liquidatorBalance = await vToken.balanceOf(liquidator1Signer._address); + + await vToken + .connect(supplier) + .transfer(liquidator1Signer._address, supplierBalance.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: supplier._address, + }); + + expect(accountVToken.vTokenBalanceMantissa).to.equal(supplierBalance.toString()); + + const { + data: { accountVToken: accountVTokenLiquidator }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: liquidator1Signer._address, + }); + expect(accountVTokenLiquidator.vTokenBalanceMantissa).to.equal( + liquidatorBalance.add(supplierBalance).toString(), + ); + } + }); + }); + + describe('Updated VToken events', () => { + it('should update the supplier count on the market when new minting', async function () { + // Supply all accounts + const usdc1000Usd = await oracle.getAssetTokenAmount( + vUsdcToken.address, + parseUnits('1000', 36), + ); + const bnb1000Usd = await oracle.getAssetTokenAmount( + vWBnbToken.address, + parseUnits('1000', 36), + ); + const eth2000Usd = await oracle.getAssetTokenAmount( + vEthToken.address, + parseUnits('2000', 36), + ); + + for (const account of suppliers) { + await vUsdcToken.connect(account).mint(usdc1000Usd.toString()); + await vWBnbToken.connect(account).mint({ value: bnb1000Usd.toString() }); + await vEthToken.connect(account).mint(eth2000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of suppliers) { + for (const vToken of [vUsdcToken, vWBnbToken, vEthToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + + expect(market?.supplierCount).to.equal('3'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + } + } + + for (const account of borrowers) { + await vUsdcToken.connect(rootSigner).mintBehalf(account._address, usdc1000Usd.toString()); + await vEthToken.connect(rootSigner).mintBehalf(account._address, eth2000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of borrowers) { + for (const vToken of [vUsdcToken, vEthToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + + expect(market?.supplierCount).to.equal('6'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + } + } + }); + + it('should not update the supplier count on the market when current accounts mint again', async function () { + // Supply all accounts + const usdc100Usd = await oracle.getAssetTokenAmount( + vUsdcToken.address, + parseUnits('1000', 36), + ); + const bnb1000Usd = await oracle.getAssetTokenAmount( + vWBnbToken.address, + parseUnits('1000', 36), + ); + const eth2000Usd = await oracle.getAssetTokenAmount( + vEthToken.address, + parseUnits('2000', 36), + ); + + for (const account of suppliers) { + await vUsdcToken.connect(account).mint(usdc100Usd.toString()); + await vWBnbToken.connect(account).mint({ value: bnb1000Usd.toString() }); + await vEthToken.connect(account).mint(eth2000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of suppliers) { + for (const vToken of [vUsdcToken, vEthToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + } + await checkSupply(account._address, vWBnbToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vWBnbToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('3'); + } + + for (const account of borrowers) { + await vUsdcToken.connect(rootSigner).mintBehalf(account._address, usdc100Usd.toString()); + await vEthToken.connect(rootSigner).mintBehalf(account._address, eth2000Usd.toString()); + } + + await waitForSubgraphToBeSynced(syncDelay); + + for (const account of borrowers) { + for (const vToken of [vUsdcToken, vEthToken]) { + await checkSupply(account._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + } + } + }); + + it('should not update the supplier count on the market when partially redeeming', async function () { + for (const vToken of [vUsdcToken, vEthToken]) { + const redeemAmount = (await vToken.balanceOf(supplier2Signer._address)).div(2); + const exchangeRate = await vToken.callStatic.exchangeRateCurrent(); + await vToken.connect(supplier2Signer).redeem(redeemAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkSupply(supplier2Signer._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('6'); + expect(market?.totalSupplyVTokenMantissa).to.equal(await vToken.totalSupply()); + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: supplier2Signer._address, + }); + expect(accountVToken.totalUnderlyingRedeemedMantissa).to.equal( + formatUnits(redeemAmount.mul(exchangeRate), 18).split('.')[0], + ); // remove decimals + } + }); + + it('should update the supplier count on the market when fully redeemed', async function () { + for (const vToken of [vUsdcToken, vEthToken]) { + const redeemAmount = await vToken.balanceOf(supplier2Signer._address); + await vToken.connect(supplier2Signer).redeem(redeemAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkSupply(supplier2Signer._address, vToken); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.supplierCount).to.equal('5'); + } + }); + + it('should handle enter market', async function () { + await comptroller + .connect(borrower1Signer) + .enterMarkets([vUsdcToken.address, vWBnbToken.address, vEthToken.address]); + await comptroller + .connect(borrower2Signer) + .enterMarkets([vUsdcToken.address, vWBnbToken.address, vEthToken.address]); + await comptroller + .connect(borrower3Signer) + .enterMarkets([vUsdcToken.address, vWBnbToken.address, vEthToken.address]); + + await waitForSubgraphToBeSynced(syncDelay); + + for (const vTokenAddress of [vUsdcToken.address, vWBnbToken.address, vEthToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + + for (const vTokenAddress of [vUsdcToken.address, vWBnbToken.address, vEthToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + + for (const vTokenAddress of [vUsdcToken.address, vWBnbToken.address, vEthToken.address]) { + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vTokenAddress.toLowerCase(), + accountId: borrower3Signer._address, + }); + expect(accountVToken?.enteredMarket).to.equal(true); + } + }); + + it('should handle exit market', async function () { + await comptroller.connect(borrower1Signer).exitMarket(vWBnbToken.address); + await comptroller.connect(borrower2Signer).exitMarket(vEthToken.address); + await comptroller.connect(borrower3Signer).exitMarket(vUsdcToken.address); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken: accountVTokenVWbnb }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vWBnbToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVTokenVWbnb?.enteredMarket).to.equal(false); + + const { + data: { accountVToken: accountVTokenVEth }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vEthToken.address.toLowerCase(), + accountId: borrower2Signer._address, + }); + expect(accountVTokenVEth?.enteredMarket).to.equal(false); + + const { + data: { accountVToken: accountVTokenVUsdc }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vUsdcToken.address.toLowerCase(), + accountId: borrower3Signer._address, + }); + expect(accountVTokenVUsdc?.enteredMarket).to.equal(false); + }); + + it('should update the borrower count on the market for new borrows', async function () { + const eth3000Usd = await oracle.getAssetTokenAmount( + vEthToken.address, + parseUnits('3000', 36), + ); + + const bnb500Usd = await oracle.getAssetTokenAmount(vWBnbToken.address, parseUnits('500', 36)); + const usdc500Usd = await oracle.getAssetTokenAmount( + vUsdcToken.address, + parseUnits('500', 36), + ); + + await vEthToken.connect(borrower1Signer).borrow(eth3000Usd); + await vWBnbToken.connect(borrower2Signer).borrow(bnb500Usd); + await vUsdcToken.connect(borrower3Signer).borrow(usdc500Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower1Signer._address, vEthToken); + await checkBorrows(borrower1Signer._address, vWBnbToken); + await checkBorrows(borrower1Signer._address, vUsdcToken); + + for (const vToken of [vUsdcToken, vWBnbToken, vEthToken]) { + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vToken.totalBorrows()).toString()); + } + }); + + it('should not update the borrower count on the market for repeated borrows', async function () { + const eth2010Usd = await oracle.getAssetTokenAmount( + vEthToken.address, + parseUnits('2010', 36), + ); + + await vEthToken.connect(borrower1Signer).borrow(eth2010Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower1Signer._address, vEthToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vEthToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vEthToken.totalBorrows()).toString()); + }); + + it('should not update the borrower count on the market for partial repayment of borrow', async function () { + const eth20Usd = await oracle.getAssetTokenAmount(vEthToken.address, parseUnits('20', 36)); + await vEthToken.connect(borrower1Signer).repayBorrow(eth20Usd); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower1Signer._address, vEthToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vEthToken.address.toLowerCase()); + expect(market?.borrowerCount).to.equal('1'); + expect(market?.totalBorrowsMantissa).to.equal((await vEthToken.totalBorrows()).toString()); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vEthToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVToken.totalUnderlyingRepaidMantissa).to.equal(eth20Usd.toString()); + }); + + it('should handle accrue interest event', async function () { + for (const vToken of [vUsdcToken, vEthToken]) { + const prevAccrualBlockNumber = await vToken.accrualBlockNumber(); + const prevBorrowIndex = await vToken.borrowIndex(); + const prevTotalBorrows = await vToken.totalBorrows(); + const prevCash = await vToken.getCash(); + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + await vToken.accrueInterest(); + + await waitForSubgraphToBeSynced(syncDelay); + expect(market.accrualBlockNumber).to.be.greaterThanOrEqual(prevAccrualBlockNumber); + expect(market.borrowIndexMantissa).to.be.greaterThanOrEqual(prevBorrowIndex); + expect(market.totalBorrowsMantissa).to.greaterThanOrEqual(prevTotalBorrows); + expect(market.cashMantissa).to.lessThanOrEqual(prevCash); + } + }); + + it('should handle accrue interest event with added reserves', async function () { + const usdc10Usd = await oracle.getAssetTokenAmount(vUsdcToken.address, parseUnits('10', 36)); + const eth50Usd = await oracle.getAssetTokenAmount(vEthToken.address, parseUnits('50', 36)); + for (const [vToken, amount] of [ + [vUsdcToken, usdc10Usd], + [vEthToken, eth50Usd], + ]) { + const borrowRateMantissaPrev = await vToken.borrowRatePerBlock(); + const exchangeRateMantissaPrev = await vToken.callStatic.exchangeRateCurrent(); + const supplyRateMantissaPrev = await vToken.supplyRatePerBlock(); + + await vToken._addReserves(amount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vToken.address.toLowerCase()); + + expect(market.reservesMantissa).to.equal(amount); + expect(market.borrowRateMantissa).to.be.greaterThanOrEqual(borrowRateMantissaPrev); + expect(market.exchangeRateMantissa).to.be.greaterThanOrEqual(exchangeRateMantissaPrev); + expect(market.supplyRateMantissa).to.be.greaterThanOrEqual(supplyRateMantissaPrev); + } + }); + + it('should handle liquidateBorrow event', async function () { + await oracle.setPrice(vEthToken.address, parseUnits('50000', 18).toString()); + await oracle.setPrice(vUsdcToken.address, parseUnits('0.9', 18).toString()); + const eth200Usd = await oracle.getAssetTokenAmount(vEthToken.address, parseUnits('200', 36)); + await vEthToken + .connect(liquidator1Signer) + .liquidateBorrow(borrower1Signer._address, eth200Usd.toString(), vUsdcToken.address); + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vEthToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + + expect(accountVToken.storedBorrowBalanceMantissa).to.be.approximately( + ethers.BigNumber.from( + await vEthToken.callStatic.borrowBalanceCurrent(borrower1Signer._address), + ), + 1e11, + ); + + const { + data: { account: accountBorrower }, + } = await subgraphClient.getAccountById(borrower1Signer._address); + expect(accountBorrower.countLiquidated).to.equal(1); + + const { + data: { account: accountLiquidator }, + } = await subgraphClient.getAccountById(liquidator1Signer._address); + expect(accountLiquidator.countLiquidator).to.equal(2); + + // Reset prices + await oracle.setPrice(vEthToken.address, parseUnits('5000', 18).toString()); + await oracle.setPrice(vUsdcToken.address, parseUnits('1', 18).toString()); + }); + + it('should update the borrower count on the market for full repayment of borrow', async function () { + const { + data: { accountVToken: accountVTokenPrev }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vEthToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + const borrowBalance = await vEthToken.callStatic.borrowBalanceCurrent( + borrower1Signer._address, + ); + + const repayAmount = borrowBalance.add(1174890622); + + await vEthToken.connect(borrower1Signer).repayBorrow(repayAmount.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + await checkBorrows(borrower1Signer._address, vEthToken); + + const { + data: { market }, + } = await subgraphClient.getMarketById(vEthToken.address.toLowerCase()); + expect( + await vEthToken.callStatic.borrowBalanceCurrent(borrower1Signer._address), + ).to.be.lessThanOrEqual(10); + expect(market?.borrowerCountAdjusted).to.equal('0'); + expect(+market?.borrowerCount).to.be.lessThanOrEqual(1); + expect(market?.totalBorrowsMantissa).to.equal((await vEthToken.totalBorrows()).toString()); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vEthToken.address.toLowerCase(), + accountId: borrower1Signer._address, + }); + expect(accountVToken.totalUnderlyingRepaidMantissa).to.be.approximately( + ethers.BigNumber.from(accountVTokenPrev.totalUnderlyingRepaidMantissa).add(repayAmount), + 1e10, + ); + }); + + it('should handle transfer event', async function () { + for (const [supplier, vToken] of [ + [supplier1Signer, vUsdcToken], + [supplier2Signer, vWBnbToken], + [supplier3Signer, vEthToken], + ]) { + const supplierBalance = (await vToken.balanceOf(supplier._address)).div(2); + const liquidatorBalance = await vToken.balanceOf(liquidator1Signer._address); + + await vToken + .connect(supplier) + .transfer(liquidator1Signer._address, supplierBalance.toString()); + + await waitForSubgraphToBeSynced(syncDelay); + + const { + data: { accountVToken }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: supplier._address, + }); + + expect(accountVToken.vTokenBalanceMantissa).to.equal(supplierBalance.toString()); + + const { + data: { accountVToken: accountVTokenLiquidator }, + } = await subgraphClient.getAccountVTokenByAccountAndMarket({ + marketId: vToken.address.toLowerCase(), + accountId: liquidator1Signer._address, + }); + expect(accountVTokenLiquidator.vTokenBalanceMantissa).to.equal( + liquidatorBalance.add(supplierBalance).toString(), + ); + } + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index b4f65325..89619da2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3157,6 +3157,24 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/hardhat-chai-matchers@npm:^1.0.3": + version: 1.0.6 + resolution: "@nomicfoundation/hardhat-chai-matchers@npm:1.0.6" + dependencies: + "@ethersproject/abi": ^5.1.2 + "@types/chai-as-promised": ^7.1.3 + chai-as-promised: ^7.1.1 + deep-eql: ^4.0.1 + ordinal: ^1.0.3 + peerDependencies: + "@nomiclabs/hardhat-ethers": ^2.0.0 + chai: ^4.2.0 + ethers: ^5.0.0 + hardhat: ^2.9.4 + checksum: c388e5ed9068f2ba7c227737ab7312dd03405d5fab195247b061f2fa52e700fbd0fb65359c2d4f2086f2905bfca642c19a9122d034533edd936f89aea65ac7f2 + languageName: node + linkType: hard + "@nomicfoundation/hardhat-ethers@npm:^3.0.0": version: 3.0.5 resolution: "@nomicfoundation/hardhat-ethers@npm:3.0.5" @@ -3935,6 +3953,22 @@ __metadata: languageName: node linkType: hard +"@types/chai-as-promised@npm:^7.1.3": + version: 7.1.8 + resolution: "@types/chai-as-promised@npm:7.1.8" + dependencies: + "@types/chai": "*" + checksum: f0e5eab451b91bc1e289ed89519faf6591932e8a28d2ec9bbe95826eb73d28fe43713633e0c18706f3baa560a7d97e7c7c20dc53ce639e5d75bac46b2a50bf21 + languageName: node + linkType: hard + +"@types/chai@npm:*": + version: 4.3.16 + resolution: "@types/chai@npm:4.3.16" + checksum: bb5f52d1b70534ed8b4bf74bd248add003ffe1156303802ea367331607c06b494da885ffbc2b674a66b4f90c9ee88759790a5f243879f6759f124f22328f5e95 + languageName: node + linkType: hard + "@types/cli-progress@npm:^3.11.0": version: 3.11.5 resolution: "@types/cli-progress@npm:3.11.5" @@ -4260,7 +4294,7 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/governance-contracts@npm:2.1.0, @venusprotocol/governance-contracts@npm:^2.1.0": +"@venusprotocol/governance-contracts@npm:2.1.0, @venusprotocol/governance-contracts@npm:^2.0.0": version: 2.1.0 resolution: "@venusprotocol/governance-contracts@npm:2.1.0" dependencies: @@ -4271,13 +4305,6 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/governance-contracts@npm:^0.0.2": - version: 0.0.2 - resolution: "@venusprotocol/governance-contracts@npm:0.0.2" - checksum: 101ade6013fe3963968d37ff839a4c62aeaedd9f26d8c5ae47bdc413e6c61732b4048a1b026e4281ade5676fb1545a49ecac299f6b669a9f0c7e73723666d7e1 - languageName: node - linkType: hard - "@venusprotocol/governance-contracts@npm:^1.4.0": version: 1.4.0 resolution: "@venusprotocol/governance-contracts@npm:1.4.0" @@ -4289,14 +4316,14 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/governance-contracts@npm:^2.0.0": - version: 2.0.0 - resolution: "@venusprotocol/governance-contracts@npm:2.0.0" +"@venusprotocol/governance-contracts@npm:^2.1.0": + version: 2.2.0 + resolution: "@venusprotocol/governance-contracts@npm:2.2.0" dependencies: "@venusprotocol/solidity-utilities": 2.0.0 hardhat-deploy-ethers: ^0.3.0-beta.13 module-alias: ^2.2.2 - checksum: 18b56d951c4e68fa1edadc93ed44daa55c8b81294778a4969d940a084de6d949630eacd4702d1b92f04ad5d709963a3a0a790014871ec34b0b2f4806cebc731c + checksum: 8e6ba9b824a47cc03b296e4cfa2f9781e30e412710dd71f1e52d7b0d41d8cd6efb0b877239edadbf4eb382bda57436d96a4ee5cac6d0fae29b555ccdb68fc8c0 languageName: node linkType: hard @@ -4370,8 +4397,8 @@ __metadata: linkType: hard "@venusprotocol/protocol-reserve@npm:^2.0.0": - version: 2.1.0 - resolution: "@venusprotocol/protocol-reserve@npm:2.1.0" + version: 2.2.0 + resolution: "@venusprotocol/protocol-reserve@npm:2.2.0" dependencies: "@nomiclabs/hardhat-ethers": ^2.2.3 "@openzeppelin/contracts": ^4.8.3 @@ -4383,7 +4410,7 @@ __metadata: ethers: ^5.7.0 hardhat-deploy: ^0.11.14 module-alias: ^2.2.2 - checksum: f817dc28232fb37e11d5e87d68b0dfca867916b6b17b30288c132c3025fe5700764fdb162befbaffaf8d9cba16990507690e811d0079982687688c94c6a0437c + checksum: ae875b95c1adabcc73ca52f74bf3f875c15e8a29cc6cbdf08038542692e5c397d986424194f1e1819659b0bc17d53770e75e7f74304ecbee24e9e84dd4ef79ed languageName: node linkType: hard @@ -4401,14 +4428,14 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/solidity-utilities@npm:^2.0.0, @venusprotocol/solidity-utilities@npm:^2.0.1": +"@venusprotocol/solidity-utilities@npm:^2.0.0": version: 2.0.1 resolution: "@venusprotocol/solidity-utilities@npm:2.0.1" checksum: 057beb3a3a8d9e3c3836f089163ddd4d5149082eb333ef9bade5b76f2d1760e27e817aefe7ec0a4cef6812945c5be78df7ef2f064d881dc01d9ce5206156d1da languageName: node linkType: hard -"@venusprotocol/solidity-utilities@npm:^2.0.3": +"@venusprotocol/solidity-utilities@npm:^2.0.1, @venusprotocol/solidity-utilities@npm:^2.0.3": version: 2.0.3 resolution: "@venusprotocol/solidity-utilities@npm:2.0.3" checksum: 5f196d61989e1b276b6f2d515c0410f3af07deee9bec58a6657e61d46b1810b2da6e2880d1ec737fd410f23a035c2db47b6a3ab2274cac229cabfcf03d4424ac @@ -4431,8 +4458,8 @@ __metadata: linkType: hard "@venusprotocol/token-bridge@npm:^2.0.0": - version: 2.0.0 - resolution: "@venusprotocol/token-bridge@npm:2.0.0" + version: 2.1.0 + resolution: "@venusprotocol/token-bridge@npm:2.1.0" dependencies: "@layerzerolabs/solidity-examples": ^1.0.0 "@openzeppelin/contracts": ^4.8.3 @@ -4441,7 +4468,7 @@ __metadata: "@solidity-parser/parser": ^0.13.2 ethers: ^5.7.0 module-alias: ^2.2.2 - checksum: 9e0c77ae7d1dc9e5df18157c00052cb30ccc166c1ad0abafffe2cc8baa4ea5f64f34cb283a9754c664a982dc1dd6c1e8133bbabedc1924a1615bd1a7d69f95fb + checksum: 022163873126822b7cd84bd470b2f7bdb439cad1ac78136a5956418803bdc873360de28ab8d1e50f28ab80add900d56660c8273ac132ba47bbd3c3890b67b046 languageName: node linkType: hard @@ -4459,37 +4486,39 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/venus-protocol@npm:5.2.0": - version: 5.2.0 - resolution: "@venusprotocol/venus-protocol@npm:5.2.0" +"@venusprotocol/venus-protocol@npm:9.0.0-dev.6": + version: 9.0.0-dev.6 + resolution: "@venusprotocol/venus-protocol@npm:9.0.0-dev.6" dependencies: + "@nomicfoundation/hardhat-ethers": ^3.0.0 "@openzeppelin/contracts": 4.9.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@venusprotocol/governance-contracts": ^0.0.2 + "@venusprotocol/governance-contracts": ^2.0.0 + "@venusprotocol/protocol-reserve": ^2.0.0 + "@venusprotocol/solidity-utilities": ^2.0.1 + "@venusprotocol/token-bridge": ^2.0.0 bignumber.js: ^9.1.2 dotenv: ^16.0.1 module-alias: ^2.2.2 - peerDependencies: - hardhat: ^2.10.1 - checksum: 39c709ead10c1cea667cb61ba5dae0945640cd68778719581452dbc465b19e8eb73b572a78fe8fcfd42e4fe3a6820efe7b60bc38fbf290d6fb8dbe228b68703c + checksum: f16ef2e8aed04f32ac5739ad76a86e271d51da7fad7d5e799b0af8f6b361afcce284855d5d868323c65e25ece30993e9c75488bb54a9f4bb6b1285eccc81a539 languageName: node linkType: hard -"@venusprotocol/venus-protocol@npm:9.0.0-dev.6": - version: 9.0.0-dev.6 - resolution: "@venusprotocol/venus-protocol@npm:9.0.0-dev.6" +"@venusprotocol/venus-protocol@npm:9.2.0-dev.2": + version: 9.2.0-dev.2 + resolution: "@venusprotocol/venus-protocol@npm:9.2.0-dev.2" dependencies: "@nomicfoundation/hardhat-ethers": ^3.0.0 "@openzeppelin/contracts": 4.9.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@venusprotocol/governance-contracts": ^2.0.0 + "@venusprotocol/governance-contracts": ^2.1.0 "@venusprotocol/protocol-reserve": ^2.0.0 - "@venusprotocol/solidity-utilities": ^2.0.1 + "@venusprotocol/solidity-utilities": ^2.0.3 "@venusprotocol/token-bridge": ^2.0.0 bignumber.js: ^9.1.2 dotenv: ^16.0.1 module-alias: ^2.2.2 - checksum: f16ef2e8aed04f32ac5739ad76a86e271d51da7fad7d5e799b0af8f6b361afcce284855d5d868323c65e25ece30993e9c75488bb54a9f4bb6b1285eccc81a539 + checksum: 7d0a8e8d1aa4c3f78646f6049ed87c410d193f5e6bda4fc09f6d5fc56201dc679106e24694624f2aa59b315b08a17a6ea3b7c38f30fa5d6a199a75be51dfffb8 languageName: node linkType: hard @@ -5829,6 +5858,17 @@ __metadata: languageName: node linkType: hard +"chai-as-promised@npm:^7.1.1": + version: 7.1.2 + resolution: "chai-as-promised@npm:7.1.2" + dependencies: + check-error: ^1.0.2 + peerDependencies: + chai: ">= 2.1.2 < 6" + checksum: 671ee980054eb23a523875c1d22929a2ac05d89b5428e1fd12800f54fc69baf41014667b87e2368e2355ee2a3140d3e3d7d5a1f8638b07cfefd7fe38a149e3f6 + languageName: node + linkType: hard + "chai@npm:^4.3.4, chai@npm:^4.3.6": version: 4.4.1 resolution: "chai@npm:4.4.1" @@ -5938,7 +5978,7 @@ __metadata: languageName: node linkType: hard -"check-error@npm:^1.0.3": +"check-error@npm:^1.0.2, check-error@npm:^1.0.3": version: 1.0.3 resolution: "check-error@npm:1.0.3" dependencies: @@ -6568,6 +6608,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.0.1": + version: 4.1.4 + resolution: "deep-eql@npm:4.1.4" + dependencies: + type-detect: ^4.0.0 + checksum: 01c3ca78ff40d79003621b157054871411f94228ceb9b2cab78da913c606631c46e8aa79efc4aa0faf3ace3092acd5221255aab3ef0e8e7b438834f0ca9a16c7 + languageName: node + linkType: hard + "deep-eql@npm:^4.1.3": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -11306,6 +11355,13 @@ __metadata: languageName: node linkType: hard +"ordinal@npm:^1.0.3": + version: 1.0.3 + resolution: "ordinal@npm:1.0.3" + checksum: 6761c5b7606b6c4b0c22b4097dab4fe7ffcddacc49238eedf9c0ced877f5d4e4ad3f4fd43fefa1cc3f167cc54c7149267441b2ae85b81ccf13f45cf4b7947164 + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -13025,6 +13081,7 @@ __metadata: "@graphprotocol/client-cli": 3.0.0 "@graphprotocol/graph-cli": ^0.73.0 "@graphprotocol/graph-ts": ^0.32.0 + "@nomicfoundation/hardhat-chai-matchers": ^1.0.3 "@nomicfoundation/hardhat-network-helpers": ^1.0.4 "@nomiclabs/hardhat-ethers": ^2.1.1 "@nomiclabs/hardhat-etherscan": ^3.1.0 @@ -13040,7 +13097,7 @@ __metadata: "@venusprotocol/isolated-pools": 3.3.0 "@venusprotocol/oracle": 2.2.0 "@venusprotocol/protocol-reserve": 2.3.0-dev.1 - "@venusprotocol/venus-protocol": 9.0.0-dev.6 + "@venusprotocol/venus-protocol": 9.2.0-dev.2 assemblyscript: 0.19.23 chai: ^4.3.6 eslint: ^8.25.0 @@ -13935,7 +13992,7 @@ __metadata: dependencies: "@graphprotocol/client-cli": 3.0.0 "@graphprotocol/graph-cli": ^0.73.0 - "@venusprotocol/venus-protocol": 5.2.0 + "@venusprotocol/venus-protocol": 9.0.0-dev.6 "@venusprotocol/venus-protocol-orig-events": "npm:@venusprotocol/venus-protocol@2.2.1" hardhat: ^2.10.2 ts-node: ^10.9.2