diff --git a/contracts/Constants.sol b/contracts/Constants.sol index 6e9bc8e6..a734c563 100644 --- a/contracts/Constants.sol +++ b/contracts/Constants.sol @@ -40,6 +40,7 @@ abstract contract Constants { // Pricing types + uint16 internal constant PRICINGTYPE__INVALID = 0; uint16 internal constant PRICINGTYPE__PEGGED = 1; uint16 internal constant PRICINGTYPE__UNISWAP3_TWAP = 2; uint16 internal constant PRICINGTYPE__FORWARDED = 3; diff --git a/contracts/lib/UniswapV3Lib.sol b/contracts/lib/UniswapV3Lib.sol new file mode 100644 index 00000000..2c53fc8e --- /dev/null +++ b/contracts/lib/UniswapV3Lib.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../vendor/TickMath.sol"; +import "../vendor/FullMath.sol"; +import "../vendor/IUniswapV3Pool.sol"; + + +library UniswapV3Lib { + function findBestUniswapPool(address factory, bytes32 poolInitCodeHash, address underlying, address referenceAsset) internal view returns (address pool, uint24 fee) { + pool = address(0); + fee = 0; + + uint24[4] memory fees = [uint24(3000), 10000, 500, 100]; + uint128 bestLiquidity = 0; + + for (uint i = 0; i < fees.length;) { + address candidatePool = computeUniswapPoolAddress(factory, poolInitCodeHash, underlying, referenceAsset, fees[i]); + + if (candidatePool.code.length > 0) { + uint128 liquidity = IUniswapV3Pool(candidatePool).liquidity(); + + if (pool == address(0) || liquidity > bestLiquidity) { + pool = candidatePool; + fee = fees[i]; + bestLiquidity = liquidity; + } + } + + unchecked { ++i; } + } + } + + function computeUniswapPoolAddress(address factory, bytes32 poolInitCodeHash, address tokenA, address tokenB, uint24 fee) internal pure returns (address) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + + return address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + factory, + keccak256(abi.encode(tokenA, tokenB, fee)), + poolInitCodeHash + ))))); + } + + function uniswapObserve(address pool, uint ago) internal view returns (uint, uint) { + uint32[] memory secondsAgos = new uint32[](2); + + secondsAgos[0] = uint32(ago); + secondsAgos[1] = 0; + + (bool success, bytes memory data) = pool.staticcall(abi.encodeWithSelector(IUniswapV3Pool.observe.selector, secondsAgos)); + + if (!success) { + if (keccak256(data) != keccak256(abi.encodeWithSignature("Error(string)", "OLD"))) revertBytes(data); + + // The oldest available observation in the ring buffer is the index following the current (accounting for wrapping), + // since this is the one that will be overwritten next. + + (,, uint16 index, uint16 cardinality,,,) = IUniswapV3Pool(pool).slot0(); + + (uint32 oldestAvailableAge,,,bool initialized) = IUniswapV3Pool(pool).observations((index + 1) % cardinality); + + // If the following observation in a ring buffer of our current cardinality is uninitialized, then all the + // observations at higher indices are also uninitialized, so we wrap back to index 0, which we now know + // to be the oldest available observation. + + if (!initialized) (oldestAvailableAge,,,) = IUniswapV3Pool(pool).observations(0); + + // Call observe() again to get the oldest available + + ago = block.timestamp - oldestAvailableAge; + secondsAgos[0] = uint32(ago); + + (success, data) = pool.staticcall(abi.encodeWithSelector(IUniswapV3Pool.observe.selector, secondsAgos)); + if (!success) revertBytes(data); + } + + // If uniswap pool doesn't exist, then data will be empty and this decode will throw: + + int56[] memory tickCumulatives = abi.decode(data, (int56[])); // don't bother decoding the liquidityCumulatives array + + int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(int(ago))); + + return (TickMath.getSqrtRatioAtTick(tick), ago); + } + + function revertBytes(bytes memory errMsg) internal pure { + if (errMsg.length > 0) { + assembly { + revert(add(32, errMsg), mload(errMsg)) + } + } + + revert("e/uniswap-v3-twap-empty-error"); + } +} diff --git a/contracts/modules/Markets.sol b/contracts/modules/Markets.sol index b0bd366c..33b530b5 100644 --- a/contracts/modules/Markets.sol +++ b/contracts/modules/Markets.sol @@ -11,6 +11,13 @@ import "../PToken.sol"; contract Markets is BaseLogic { constructor(bytes32 moduleGitCommit_) BaseLogic(MODULEID__MARKETS, moduleGitCommit_) {} + modifier governorOnly { + address msgSender = unpackTrailingParamMsgSender(); + + require(msgSender == governorAdmin, "e/markets/unauthorized"); + _; + } + /// @notice Create an Euler pool and associated EToken and DToken addresses. /// @param underlying The address of an ERC20-compliant token. There must be an initialised uniswap3 pool for the underlying/reference asset pair. /// @return The created EToken, or the existing EToken if already activated. @@ -19,6 +26,24 @@ contract Markets is BaseLogic { return doActivateMarket(underlying); } + function activateMarketWithChainlinkPriceFeed(address underlying, address chainlinkAggregator) external nonReentrant governorOnly returns (address) { + require(pTokenLookup[underlying] == address(0), "e/markets/invalid-token"); + require(chainlinkPriceFeedLookup[underlying] == address(0), "e/market/underlying-already-activated"); + require(chainlinkAggregator != address(0), "e/markets/bad-chainlink-address"); + + chainlinkPriceFeedLookup[underlying] = chainlinkAggregator; + emit GovSetChainlinkPriceFeed(underlying, chainlinkAggregator); + + address eTokenAddr = underlyingLookup[underlying].eTokenAddress; + if (eTokenAddr == address(0)) { + return doActivateMarket(underlying); + } else { // already activated + AssetStorage storage assetStorage = eTokenLookup[eTokenAddr]; + assetStorage.pricingType = PRICINGTYPE__CHAINLINK; + return eTokenAddr; + } + } + function doActivateMarket(address underlying) private returns (address) { // Pre-existing @@ -43,6 +68,11 @@ contract Markets is BaseLogic { (params) = abi.decode(result, (IRiskManager.NewMarketParameters)); } + if (chainlinkPriceFeedLookup[underlying] != address(0)) { + params.pricingType = PRICINGTYPE__CHAINLINK; + } + + require(params.pricingType != PRICINGTYPE__INVALID, "e/markets/pricing-type-invalid"); // Create proxies diff --git a/contracts/modules/RiskManager.sol b/contracts/modules/RiskManager.sol index 9d0790eb..2e1b0e1a 100644 --- a/contracts/modules/RiskManager.sol +++ b/contracts/modules/RiskManager.sol @@ -4,23 +4,9 @@ pragma solidity ^0.8.0; import "../BaseLogic.sol"; import "../IRiskManager.sol"; -import "../vendor/TickMath.sol"; -import "../vendor/FullMath.sol"; +import "../lib/UniswapV3Lib.sol"; - -interface IUniswapV3Factory { - function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool); -} - -interface IUniswapV3Pool { - function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked); - function liquidity() external view returns (uint128); - function observe(uint32[] calldata secondsAgos) external view returns (int56[] memory tickCumulatives, uint160[] memory liquidityCumulatives); - function observations(uint256 index) external view returns (uint32 blockTimestamp, int56 tickCumulative, uint160 liquidityCumulative, bool initialized); - function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; -} - interface IChainlinkAggregatorV2V3 { function latestAnswer() external view returns (int256); } @@ -48,6 +34,8 @@ contract RiskManager is IRiskManager, BaseLogic { // Default market parameters function getNewMarketParameters(address underlying) external override returns (NewMarketParameters memory p) { + p.pricingType = PRICINGTYPE__INVALID; + p.pricingParameters = uint32(0); p.config.borrowIsolated = true; p.config.collateralFactor = uint32(0); p.config.borrowFactor = type(uint32).max; @@ -63,7 +51,7 @@ contract RiskManager is IRiskManager, BaseLogic { p.pricingParameters = uint32(0); p.config.collateralFactor = underlyingLookup[pTokenLookup[underlying]].collateralFactor; - } else { + } else if (uniswapFactory != address(0)) { // Uniswap3 TWAP // The uniswap pool (fee-level) with the highest in-range liquidity is used by default. @@ -71,40 +59,12 @@ contract RiskManager is IRiskManager, BaseLogic { // verify the selection is suitable before using the pool. Otherwise, governance will // need to change the pricing config for the market. - address pool = address(0); - uint24 fee = 0; - - { - uint24[4] memory fees = [uint24(3000), 10000, 500, 100]; - uint128 bestLiquidity = 0; - - for (uint i = 0; i < fees.length; ++i) { - address candidatePool = IUniswapV3Factory(uniswapFactory).getPool(underlying, referenceAsset, fees[i]); - if (candidatePool == address(0)) continue; - - uint128 liquidity = IUniswapV3Pool(candidatePool).liquidity(); - - if (pool == address(0) || liquidity > bestLiquidity) { - pool = candidatePool; - fee = fees[i]; - bestLiquidity = liquidity; - } - } - } - - require(pool != address(0), "e/no-uniswap-pool-avail"); - require(computeUniswapPoolAddress(underlying, fee) == pool, "e/bad-uniswap-pool-addr"); - - p.pricingType = PRICINGTYPE__UNISWAP3_TWAP; - p.pricingParameters = uint32(fee); - - try IUniswapV3Pool(pool).increaseObservationCardinalityNext(MIN_UNISWAP3_OBSERVATION_CARDINALITY) { - // Success - } catch Error(string memory err) { - if (keccak256(bytes(err)) == keccak256("LOK")) revert("e/risk/uniswap-pool-not-inited"); - revert(string(abi.encodePacked("e/risk/uniswap/", err))); - } catch (bytes memory returnData) { - revertBytes(returnData); + (address pool, uint24 fee) = UniswapV3Lib.findBestUniswapPool(uniswapFactory, uniswapPoolInitCodeHash, underlying, referenceAsset); + if (pool != address(0)) { + try IUniswapV3Pool(pool).increaseObservationCardinalityNext(MIN_UNISWAP3_OBSERVATION_CARDINALITY) { + p.pricingType = PRICINGTYPE__UNISWAP3_TWAP; + p.pricingParameters = uint32(fee); + } catch {} } } } @@ -113,20 +73,6 @@ contract RiskManager is IRiskManager, BaseLogic { // Pricing - function computeUniswapPoolAddress(address underlying, uint24 fee) private view returns (address) { - address tokenA = underlying; - address tokenB = referenceAsset; - if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - - return address(uint160(uint256(keccak256(abi.encodePacked( - hex'ff', - uniswapFactory, - keccak256(abi.encode(tokenA, tokenB, fee)), - uniswapPoolInitCodeHash - ))))); - } - - function decodeSqrtPriceX96(address underlying, uint underlyingDecimalsScaler, uint sqrtPriceX96) private view returns (uint price) { if (uint160(underlying) < uint160(referenceAsset)) { price = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, uint(2**(96*2)) / 1e18) / underlyingDecimalsScaler; @@ -140,46 +86,9 @@ contract RiskManager is IRiskManager, BaseLogic { else if (price == 0) price = 1; } - function callUniswapObserve(address underlying, uint underlyingDecimalsScaler, address pool, uint ago) private view returns (uint, uint) { - uint32[] memory secondsAgos = new uint32[](2); - - secondsAgos[0] = uint32(ago); - secondsAgos[1] = 0; - - (bool success, bytes memory data) = pool.staticcall(abi.encodeWithSelector(IUniswapV3Pool.observe.selector, secondsAgos)); - - if (!success) { - if (keccak256(data) != keccak256(abi.encodeWithSignature("Error(string)", "OLD"))) revertBytes(data); - - // The oldest available observation in the ring buffer is the index following the current (accounting for wrapping), - // since this is the one that will be overwritten next. - - (,, uint16 index, uint16 cardinality,,,) = IUniswapV3Pool(pool).slot0(); - - (uint32 oldestAvailableAge,,,bool initialized) = IUniswapV3Pool(pool).observations((index + 1) % cardinality); - - // If the following observation in a ring buffer of our current cardinality is uninitialized, then all the - // observations at higher indices are also uninitialized, so we wrap back to index 0, which we now know - // to be the oldest available observation. - - if (!initialized) (oldestAvailableAge,,,) = IUniswapV3Pool(pool).observations(0); - - // Call observe() again to get the oldest available - - ago = block.timestamp - oldestAvailableAge; - secondsAgos[0] = uint32(ago); - - (success, data) = pool.staticcall(abi.encodeWithSelector(IUniswapV3Pool.observe.selector, secondsAgos)); - if (!success) revertBytes(data); - } - - // If uniswap pool doesn't exist, then data will be empty and this decode will throw: - - int56[] memory tickCumulatives = abi.decode(data, (int56[])); // don't bother decoding the liquidityCumulatives array - - int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(int(ago))); - - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick); + function callUniswapObserve(address underlying, uint pricingParameters, uint twapWindow, uint underlyingDecimalsScaler) private view returns (uint, uint) { + address pool = UniswapV3Lib.computeUniswapPoolAddress(uniswapFactory, uniswapPoolInitCodeHash, underlying, referenceAsset, uint24(pricingParameters)); + (uint sqrtPriceX96, uint ago) = UniswapV3Lib.uniswapObserve(pool, twapWindow); return (decodeSqrtPriceX96(underlying, underlyingDecimalsScaler, sqrtPriceX96), ago); } @@ -229,16 +138,14 @@ contract RiskManager is IRiskManager, BaseLogic { twap = 1e18; twapPeriod = twapWindow; } else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP) { - address pool = computeUniswapPoolAddress(underlying, uint24(pricingParameters)); - (twap, twapPeriod) = callUniswapObserve(underlying, underlyingDecimalsScaler, pool, twapWindow); + (twap, twapPeriod) = callUniswapObserve(underlying, pricingParameters, twapWindow, underlyingDecimalsScaler); } else if (pricingType == PRICINGTYPE__CHAINLINK) { twap = callChainlinkLatestAnswer(chainlinkPriceFeedLookup[underlying]); twapPeriod = 0; // if price invalid and uniswap fallback pool configured get the price from uniswap if (twap == 0 && uint24(pricingParameters) != 0) { - address pool = computeUniswapPoolAddress(underlying, uint24(pricingParameters)); - (twap, twapPeriod) = callUniswapObserve(underlying, underlyingDecimalsScaler, pool, twapWindow); + (twap, twapPeriod) = callUniswapObserve(underlying, pricingParameters, twapWindow, underlyingDecimalsScaler); } require(twap != 0, "e/unable-to-get-the-price"); @@ -269,8 +176,8 @@ contract RiskManager is IRiskManager, BaseLogic { if (pricingType == PRICINGTYPE__PEGGED) { currPrice = 1e18; - } else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP || pricingType == PRICINGTYPE__FORWARDED) { - address pool = computeUniswapPoolAddress(newUnderlying, uint24(pricingParameters)); + } else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP) { + address pool = UniswapV3Lib.computeUniswapPoolAddress(uniswapFactory, uniswapPoolInitCodeHash, underlying, referenceAsset, uint24(pricingParameters)); (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); currPrice = decodeSqrtPriceX96(newUnderlying, underlyingDecimalsScaler, sqrtPriceX96); } else if (pricingType == PRICINGTYPE__CHAINLINK) { diff --git a/contracts/vendor/IUniswapV3Pool.sol b/contracts/vendor/IUniswapV3Pool.sol new file mode 100644 index 00000000..8be6641f --- /dev/null +++ b/contracts/vendor/IUniswapV3Pool.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +/// @title Uniswap V3 Pool +/// @notice Minimal interface for the Uniswap V3 Pool +interface IUniswapV3Pool { + function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked); + function liquidity() external view returns (uint128); + function observe(uint32[] calldata secondsAgos) external view returns (int56[] memory tickCumulatives, uint160[] memory liquidityCumulatives); + function observations(uint256 index) external view returns (uint32 blockTimestamp, int56 tickCumulative, uint160 liquidityCumulative, bool initialized); + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} diff --git a/test/activateMarket.js b/test/activateMarket.js index 2a5faf63..c910222e 100644 --- a/test/activateMarket.js +++ b/test/activateMarket.js @@ -1,12 +1,16 @@ const et = require('./lib/eTestLib'); +const PRICINGTYPE__UNISWAP3_TWAP = 2 +const PRICINGTYPE__CHAINLINK = 4 +const NON_ZERO_ADDRESS = '0x0000000000000000000000000000000000000001' + et.testSet({ desc: "activating markets", }) .test({ - desc: "re-activate", + desc: "re-activate after uniswap activation", actions: ctx => [ { from: ctx.wallet, send: 'markets.activateMarket', args: [ctx.contracts.tokens.UTST.address], }, @@ -19,6 +23,71 @@ et.testSet({ { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.UTST.address], onResult: r => { et.expect(ctx.stash.eTokenAddr).to.equal(r); }}, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.UTST.address], onResult: r => { + et.expect(PRICINGTYPE__UNISWAP3_TWAP).to.equal(r.pricingType); + }}, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.UTST.address], onResult: r => { + et.expect(et.AddressZero).to.equal(r); + }}, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.UTST.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.UTST.address], onResult: r => { + et.expect(ctx.stash.eTokenAddr).to.equal(r); + }}, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.UTST.address], onResult: r => { + et.expect(PRICINGTYPE__CHAINLINK).to.equal(r.pricingType); + }}, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.UTST.address], onResult: r => { + et.expect(NON_ZERO_ADDRESS).to.equal(r); + }}, + ], +}) + + +.test({ + desc: "re-activate after chainlink activation", + actions: ctx => [ + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.UTST2.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + ctx.stash.eTokenAddr = r; + }}, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + et.expect(PRICINGTYPE__CHAINLINK).to.equal(r.pricingType); + }}, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + et.expect(NON_ZERO_ADDRESS).to.equal(r); + }}, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.UTST2.address, NON_ZERO_ADDRESS], + expectError: 'e/market/underlying-already-activated' + }, + + { from: ctx.wallet, send: 'markets.activateMarket', args: [ctx.contracts.tokens.UTST2.address], }, + + { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + et.expect(ctx.stash.eTokenAddr).to.equal(r); + }}, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + et.expect(PRICINGTYPE__CHAINLINK).to.equal(r.pricingType); + }}, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.UTST2.address], onResult: r => { + et.expect(NON_ZERO_ADDRESS).to.equal(r); + }}, ], }) @@ -37,43 +106,119 @@ et.testSet({ .test({ desc: "no uniswap pool", actions: ctx => [ - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/no-uniswap-pool-avail', }, + // error for permissionless activation + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/markets/pricing-type-invalid', }, + + // succeeds for permissioned activation with Chainlink + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(0); + }, }, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) .test({ - desc: "uniswap pool not initiated", + desc: "pricing type invalid due to uniswap pool not initiated", actions: ctx => [ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, async () => { await (await ctx.contracts.uniswapPools['TST4/WETH'].mockSetThrowNotInitiated(true)).wait(); }, - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/risk/uniswap-pool-not-inited', }, + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/markets/pricing-type-invalid', }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(0); + }, }, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) .test({ - desc: "uniswap pool other error", + desc: "pricing type invalid due to uniswap pool other error", actions: ctx => [ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, async () => { await (await ctx.contracts.uniswapPools['TST4/WETH'].mockSetThrowOther(true)).wait(); }, - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/risk/uniswap/OTHER', }, + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/markets/pricing-type-invalid', }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(0); + }, }, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) .test({ - desc: "uniswap pool empty error", + desc: "pricing type invalid due to uniswap pool empty error", actions: ctx => [ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, async () => { await (await ctx.contracts.uniswapPools['TST4/WETH'].mockSetThrowEmpty(true)).wait(); }, - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/empty-error', }, + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/markets/pricing-type-invalid', }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(0); + }, }, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, + ], +}) + + +.test({ + desc: "activation with chainlink - non-governor", + actions: ctx => [ + { from: ctx.wallet2, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, et.AddressZero], + expectError: 'e/markets/unauthorized' + }, + ], +}) + + +.test({ + desc: "activation with chainlink - bad chainlink address", + actions: ctx => [ + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, et.AddressZero], + expectError: 'e/markets/bad-chainlink-address' + }, ], }) @@ -84,8 +229,30 @@ et.testSet({ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.LOW, }, { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], }, { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__UNISWAP3_TWAP); + et.expect(r.pricingParameters).to.equal(et.FeeAmount.LOW); + }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(et.AddressZero); + }, }, + ], +}) + + +.test({ + desc: "select second fee uniswap pool with chainlink", + actions: ctx => [ + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.LOW, }, + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); et.expect(r.pricingParameters).to.equal(et.FeeAmount.LOW); }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) @@ -96,8 +263,30 @@ et.testSet({ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.HIGH, }, { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], }, { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__UNISWAP3_TWAP); et.expect(r.pricingParameters).to.equal(et.FeeAmount.HIGH); }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(et.AddressZero); + }, }, + ], +}) + + +.test({ + desc: "select third fee uniswap pool with chainlink", + actions: ctx => [ + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.HIGH, }, + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(et.FeeAmount.HIGH); + }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) @@ -117,8 +306,38 @@ et.testSet({ { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], }, { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__UNISWAP3_TWAP); + et.expect(r.pricingParameters).to.equal(et.FeeAmount.LOW); + }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(et.AddressZero); + }, }, + ], +}) + + +.test({ + desc: "choose pool with best liquidity with chainlink", + actions: ctx => [ + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [6000], }, + + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.LOW, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [9000], }, + + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.HIGH, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [7000], }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); et.expect(r.pricingParameters).to.equal(et.FeeAmount.LOW); }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) @@ -134,8 +353,35 @@ et.testSet({ { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], }, { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__UNISWAP3_TWAP); et.expect(r.pricingParameters).to.equal(et.FeeAmount.HIGH); }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(et.AddressZero); + }, }, + ], +}) + + +.test({ + desc: "choose pool with best liquidity, 2, with chainlink", + actions: ctx => [ + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [6000], }, + + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.HIGH, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [7000], }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(et.FeeAmount.HIGH); + }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, ], }) @@ -151,27 +397,35 @@ et.testSet({ { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], }, { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__UNISWAP3_TWAP); et.expect(r.pricingParameters).to.equal(et.FeeAmount.MEDIUM); }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(et.AddressZero); + }, }, ], }) .test({ - desc: "pool address computation", + desc: "choose pool with best liquidity, 3, with chainlink", actions: ctx => [ { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.MEDIUM, }, - { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.LOW, }, - - // Make it so that getPool(LOW) returns the pool for MEDIUM, to cause the CREATE2 address computation to fail + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [7000], }, - { action: 'cb', cb: async () => { - let lowPool = await ctx.contracts.uniswapV3Factory.getPool(ctx.contracts.tokens.TST4.address, ctx.contracts.tokens.WETH.address, et.FeeAmount.LOW); + { action: 'createUniswapPool', pair: 'TST4/WETH', fee: et.FeeAmount.HIGH, }, + { send: 'uniswapPools.TST4/WETH.mockSetLiquidity', args: [6000], }, - await ctx.contracts.uniswapV3Factory.setPoolAddress(ctx.contracts.tokens.TST4.address, ctx.contracts.tokens.WETH.address, et.FeeAmount.MEDIUM, lowPool); + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST4.address, NON_ZERO_ADDRESS], + }, + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(et.FeeAmount.MEDIUM); + }, }, + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST4.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); }, }, - - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST4.address], expectError: 'e/bad-uniswap-pool-addr'}, ], }) diff --git a/test/activateMarketNoUniswap.js b/test/activateMarketNoUniswap.js new file mode 100644 index 00000000..c85c475e --- /dev/null +++ b/test/activateMarketNoUniswap.js @@ -0,0 +1,92 @@ +const et = require('./lib/eTestLib'); + +const PRICINGTYPE__CHAINLINK = 4 +const NON_ZERO_ADDRESS = '0x0000000000000000000000000000000000000001' + +et.testSet({ + desc: "activating markets without uniswap", + fixture: 'testing-no-uniswap' +}) + + +.test({ + desc: "re-activate", + actions: ctx => [ + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.WETH.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.WETH.address], onResult: r => { + ctx.stash.eTokenAddr = r; + }}, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.WETH.address, NON_ZERO_ADDRESS], + expectError: 'e/market/underlying-already-activated' + }, + + { from: ctx.wallet, send: 'markets.activateMarket', args: [ctx.contracts.tokens.WETH.address], }, + + { call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.WETH.address], onResult: r => { + et.expect(ctx.stash.eTokenAddr).to.equal(r); + }}, + ], +}) + + +.test({ + desc: "invalid contracts", + actions: ctx => [ + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.euler.address, NON_ZERO_ADDRESS], + expectError: 'e/markets/invalid-token', + }, + + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.WETH.address, NON_ZERO_ADDRESS], + }, + { action: 'cb', cb: async () => { + const eWETH = await ctx.contracts.markets.underlyingToEToken(ctx.contracts.tokens.WETH.address); + const dWETH = await ctx.contracts.markets.underlyingToDToken(ctx.contracts.tokens.WETH.address); + + let msg; + await ctx.contracts.markets.activateMarketWithChainlinkPriceFeed(eWETH, NON_ZERO_ADDRESS) + .catch(e => { + msg = e.message; + }); + et.expect(msg).to.contains('e/markets/invalid-token'); + + msg = "" + await ctx.contracts.markets.activateMarketWithChainlinkPriceFeed(dWETH, NON_ZERO_ADDRESS) + .catch(e => { + msg = e.message; + }); + et.expect(msg).to.contains('e/markets/invalid-token'); + } }, + ], +}) + + +.test({ + desc: "no uniswap factory", + actions: ctx => [ + // error for permissionless activation + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST.address], expectError: 'e/markets/pricing-type-invalid', }, + + // succeeds for permissioned activation with Chainlink + { from: ctx.wallet, send: 'markets.activateMarketWithChainlinkPriceFeed', + args: [ctx.contracts.tokens.TST.address, NON_ZERO_ADDRESS], + }, + + { call: 'markets.getPricingConfig', args: [ctx.contracts.tokens.TST.address], onResult: r => { + et.expect(r.pricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(r.pricingParameters).to.equal(0); + }, }, + + { call: 'markets.getChainlinkPriceFeedConfig', args: [ctx.contracts.tokens.TST.address], onResult: r => { + et.expect(r).to.equal(NON_ZERO_ADDRESS); + }, }, + ], +}) + +.run(); diff --git a/test/decimalsAbove18.js b/test/decimalsAbove18.js index 8ce649d4..56f2f49b 100644 --- a/test/decimalsAbove18.js +++ b/test/decimalsAbove18.js @@ -8,7 +8,7 @@ et.testSet({ .test({ desc: "names, symbols and decimals", actions: ctx => [ - { call: 'tokens.UTST.name', args: [], assertEql: 'Unactivated Test Token', }, + { call: 'tokens.UTST.name', args: [], assertEql: 'Unactivated Test Token 1', }, { call: 'tokens.UTST.symbol', args: [], assertEql: 'UTST', }, { call: 'tokens.UTST.decimals', args: [], equals: [18], }, @@ -45,4 +45,4 @@ et.testSet({ }) -.run(); \ No newline at end of file +.run(); diff --git a/test/lib/eTestLib.js b/test/lib/eTestLib.js index a6592d28..3b078da7 100644 --- a/test/lib/eTestLib.js +++ b/test/lib/eTestLib.js @@ -762,6 +762,12 @@ async function deployContracts(provider, wallets, tokenSetupName) { if (ctx.tokenSetup.riskManagerSettings) { riskManagerSettings = ctx.tokenSetup.riskManagerSettings; + } else if (ctx.tokenSetup.dontUseUniswap) { + riskManagerSettings = { + referenceAsset: ctx.contracts.tokens['WETH'].address, + uniswapFactory: ethers.constants.AddressZero, + uniswapPoolInitCodeHash: ctx.uniswapV3PoolByteCodeHash, + } } else { riskManagerSettings = { referenceAsset: ctx.contracts.tokens['WETH'].address, diff --git a/test/lib/token-setups/testing-no-uniswap.js b/test/lib/token-setups/testing-no-uniswap.js new file mode 100644 index 00000000..3c8604f0 --- /dev/null +++ b/test/lib/token-setups/testing-no-uniswap.js @@ -0,0 +1,109 @@ +module.exports = { + dontUseUniswap: true, + testing: { + tokens: [ + { + name: "Wrapped ETH", + symbol: "WETH", + decimals: 18, + }, + { + name: "Test Token", + symbol: "TST", + decimals: 18, + }, + { + name: "Test Token 2", + symbol: "TST2", + decimals: 18, + }, + { + name: "Test Token 3", + symbol: "TST3", + decimals: 18, + }, + { + name: "Test Token 4", + symbol: "TST4", + decimals: 18, + }, + { + name: "Test Token 5", + symbol: "TST5", + decimals: 18, + }, + { + name: "Test Token 6", + symbol: "TST6", + decimals: 18, + }, + { + name: "Test Token 7", + symbol: "TST7", + decimals: 18, + }, + { + name: "Test Token 8", + symbol: "TST8", + decimals: 18, + }, + { + name: "Test Token 9", + symbol: "TST9", + decimals: 6, + }, + { + name: "Test Token 10", + symbol: "TST10", + decimals: 0, + }, + { + name: "Unactivated Test Token", + symbol: "UTST", + decimals: 18, + }, + { + name: "Test Token 11", + symbol: "TST11", + decimals: 18, + }, + { + name: "Test Token 12", + symbol: "TST12", + decimals: 8, + }, + { + name: "Test Token 13", + symbol: "TST13", + decimals: 18, + }, + { + name: "Test Token 14", + symbol: "TST14", + decimals: 18, + }, + { + name: "Euler Token", + symbol: "EUL", + decimals: 18, + }, + ], + + uniswapPools: [ + ["TST", "WETH"], + ["TST2", "WETH"], + ["TST3", "WETH"], + ["TST6", "WETH"], + ["TST9", "WETH"], + ["TST10", "WETH"], + ["TST11", "WETH"], + ["TST12", "WETH"], + ["TST13", "WETH"], + ["TST14", "WETH"], + ["UTST", "WETH"], + ], + + activated: [ + ], + }, +}; diff --git a/test/lib/token-setups/testing.js b/test/lib/token-setups/testing.js index e9156de8..e20bdd8a 100644 --- a/test/lib/token-setups/testing.js +++ b/test/lib/token-setups/testing.js @@ -68,10 +68,15 @@ module.exports = { decimals: 0, }, { - name: "Unactivated Test Token", + name: "Unactivated Test Token 1", symbol: "UTST", decimals: 18, }, + { + name: "Unactivated Test Token 2", + symbol: "UTST2", + decimals: 18, + }, { name: "Test Token 11", symbol: "TST11", diff --git a/test/pToken.js b/test/pToken.js index 9a567071..b0ba4c90 100644 --- a/test/pToken.js +++ b/test/pToken.js @@ -136,6 +136,8 @@ et.testSet({ { send: 'pTokens.pTST.wrap', args: [et.eth(11)], }, { send: 'markets.activateMarket', args: [ctx.contracts.pTokens.pTST.address], expectError: 'e/markets/invalid-token', }, { send: 'markets.activatePToken', args: [ctx.contracts.pTokens.pTST.address], expectError: 'e/nested-ptoken', }, + + { send: 'markets.activateMarketWithChainlinkPriceFeed', args: [ctx.contracts.pTokens.pTST.address, et.AddressZero], expectError: 'e/markets/invalid-token', }, ], }) diff --git a/test/permitFork.js b/test/permitFork.js index 88373679..1fabf87c 100644 --- a/test/permitFork.js +++ b/test/permitFork.js @@ -37,7 +37,7 @@ et.testSet({ ], }, { send: 'eTokens.eUSDC.deposit', args: [0, et.units(10, 6)], }, ], }, - { call: 'eTokens.eUSDC.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.units(10, 6), }, + { call: 'eTokens.eUSDC.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.units(10, 6), et.formatUnits(et.DefaultReserve)], }, { call: 'tokens.USDC.allowance', args: [ctx.wallet.address, ctx.contracts.euler.address], assertEql: 0, }, ], }) @@ -90,7 +90,7 @@ et.testSet({ ], }, { send: 'eTokens.eGRT.deposit', args: [0, et.eth(10)], }, ], }, - { call: 'eTokens.eGRT.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth(10), }, + { call: 'eTokens.eGRT.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.eth(10), et.formatUnits(et.DefaultReserve)], }, { call: 'tokens.GRT.allowance', args: [ctx.wallet.address, ctx.contracts.euler.address], assertEql: 0, }, ], }) @@ -117,7 +117,7 @@ et.testSet({ ], }, { send: 'eTokens.eDAI.deposit', args: [0, et.eth(10)], }, ], }, - { call: 'eTokens.eDAI.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth(10), }, + { call: 'eTokens.eDAI.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.eth(10), et.formatUnits(et.DefaultReserve)], }, // remove allowance { action: 'signPermit', token: 'DAI', signer: ctx.wallet, spender: ctx.contracts.euler.address, value: false, deadline: et.MaxUint256, diff --git a/test/swap1inch.js b/test/swap1inch.js index 7eebdcdf..c513a6fd 100644 --- a/test/swap1inch.js +++ b/test/swap1inch.js @@ -37,15 +37,15 @@ et.testSet({ payload: getPayload('BAT-USDT', ctx.contracts.euler.address), }]}, // total supply - { call: 'eTokens.eBAT.totalSupply', assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eBAT.totalSupplyUnderlying', assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eUSDT.totalSupply', equals: [et.eth('29921.938245')] }, - { call: 'eTokens.eUSDT.totalSupplyUnderlying', equals: [et.units('29921.938245', 6)] }, + { call: 'eTokens.eBAT.totalSupply', equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eBAT.totalSupplyUnderlying', equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eUSDT.totalSupply', equals: [et.eth('29921.938245'), 1e-6], }, + { call: 'eTokens.eUSDT.totalSupplyUnderlying', equals: [et.units('29921.938245', 6), 1e-6], }, // account balances - { call: 'eTokens.eBAT.balanceOf', args: [ctx.wallet.address], assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eBAT.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eUSDT.balanceOf', args: [ctx.wallet.address], equals: [et.eth('29921.938245')] }, - { call: 'eTokens.eUSDT.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.units('29921.938245', 6)]}, + { call: 'eTokens.eBAT.balanceOf', args: [ctx.wallet.address], equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eBAT.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eUSDT.balanceOf', args: [ctx.wallet.address], equals: [et.eth('29921.938245'), 1e-6], }, + { call: 'eTokens.eUSDT.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.units('29921.938245', 6), 1e-6], }, ], }) @@ -79,15 +79,15 @@ et.testSet({ payload: getPayload('BAT-USDT', ctx.contracts.euler.address) }]}, // total supply - { call: 'eTokens.eBAT.totalSupply', assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eBAT.totalSupplyUnderlying', assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eUSDT.totalSupply', equals: [et.eth('29921.938245')] }, - { call: 'eTokens.eUSDT.totalSupplyUnderlying', equals: [et.units('29921.938245', 6)] }, + { call: 'eTokens.eBAT.totalSupply', equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eBAT.totalSupplyUnderlying', equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eUSDT.totalSupply', equals: [et.eth('29921.938245'), 1e-6], }, + { call: 'eTokens.eUSDT.totalSupplyUnderlying', equals: [et.units('29921.938245', 6), 1e-6], }, // account balances - { call: 'eTokens.eBAT.balanceOf', args: [et.getSubAccount(ctx.wallet.address, 0)], assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eBAT.balanceOfUnderlying', args: [et.getSubAccount(ctx.wallet.address, 0)], assertEql: et.eth(100_000).sub(et.eth('25048.11267549')), }, - { call: 'eTokens.eUSDT.balanceOf', args: [et.getSubAccount(ctx.wallet.address, 1)], equals: [et.eth('29921.938245')] }, - { call: 'eTokens.eUSDT.balanceOfUnderlying', args: [et.getSubAccount(ctx.wallet.address, 1)], equals: [et.units('29921.938245', 6)] }, + { call: 'eTokens.eBAT.balanceOf', args: [et.getSubAccount(ctx.wallet.address, 0)], equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eBAT.balanceOfUnderlying', args: [et.getSubAccount(ctx.wallet.address, 0)], equals: [et.eth(100_000).sub(et.eth('25048.11267549')), 1e-6], }, + { call: 'eTokens.eUSDT.balanceOf', args: [et.getSubAccount(ctx.wallet.address, 1)], equals: [et.eth('29921.938245'), 1e-6], }, + { call: 'eTokens.eUSDT.balanceOfUnderlying', args: [et.getSubAccount(ctx.wallet.address, 1)], equals: [et.units('29921.938245', 6), 1e-6], }, ], }) @@ -106,16 +106,16 @@ et.testSet({ payload: getPayload('USDC-RGT', ctx.contracts.euler.address), }]}, // total supply - { call: 'eTokens.eUSDC.totalSupply', assertEql: et.eth(100_000).sub(et.eth('50000')), }, - { call: 'eTokens.eUSDC.totalSupplyUnderlying', assertEql: et.units(100_000, 6).sub(et.units('50000', 6)) }, - { call: 'eTokens.eRGT.totalSupply', equals: ['1263.349469909703714654', 1] }, - { call: 'eTokens.eRGT.totalSupplyUnderlying', equals: ['1263.349469909703714654', 1] }, + { call: 'eTokens.eUSDC.totalSupply', equals: [et.eth(100_000).sub(et.eth('50000')), 1e-6], }, + { call: 'eTokens.eUSDC.totalSupplyUnderlying', equals: [et.units(100_000, 6).sub(et.units('50000', 6)), 1e-6], }, + { call: 'eTokens.eRGT.totalSupply', equals: ['1263.349469909703714654', 1e-6], }, + { call: 'eTokens.eRGT.totalSupplyUnderlying', equals: ['1263.349469909703714654', 1e-6], }, // account balances - { call: 'eTokens.eUSDC.balanceOf', args: [ctx.wallet.address], assertEql: et.eth(100_000).sub(et.eth('50000')) }, - { call: 'eTokens.eUSDC.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.units(100_000, 6).sub(et.units('50000', 6)) }, + { call: 'eTokens.eUSDC.balanceOf', args: [ctx.wallet.address], equals: [et.eth(100_000).sub(et.eth('50000')), 1e-6], }, + { call: 'eTokens.eUSDC.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.units(100_000, 6).sub(et.units('50000', 6)), 1e-6], }, - { call: 'eTokens.eRGT.balanceOf', args: [ctx.wallet.address], equals: ['1263.349469909703714654', 1] }, - { call: 'eTokens.eRGT.balanceOfUnderlying', args: [ctx.wallet.address], equals: ['1263.349469909703714654', 1] }, + { call: 'eTokens.eRGT.balanceOf', args: [ctx.wallet.address], equals: ['1263.349469909703714654', 1e-6], }, + { call: 'eTokens.eRGT.balanceOfUnderlying', args: [ctx.wallet.address], equals: ['1263.349469909703714654', 1e-6], }, ], }) diff --git a/test/uniswap-integration.js b/test/uniswap-integration.js index 6f716d22..a76847c9 100644 --- a/test/uniswap-integration.js +++ b/test/uniswap-integration.js @@ -21,7 +21,7 @@ let tests = et.testSet({ // Uniswap pool has been created, but not init'ed { action: 'getPrice', underlying: 'TST', expectError: 'e/market-not-activated', }, - { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST.address], expectError: 'e/risk/uniswap-pool-not-inited', }, + { send: 'markets.activateMarket', args: [ctx.contracts.tokens.TST.address], expectError: 'e/markets/pricing-type-invalid', }, // Init uniswap pool, euler pool still not activated