diff --git a/contracts/flash-swap/ExactOutputFlashSwap.sol b/contracts/flash-swap/ExactOutputFlashSwap.sol new file mode 100644 index 00000000..b23c1305 --- /dev/null +++ b/contracts/flash-swap/ExactOutputFlashSwap.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import { IPancakeV3SwapCallback } from "@pancakeswap/v3-core/contracts/interfaces/callback/IPancakeV3SwapCallback.sol"; +import { IPancakeV3Pool } from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; + +import { approveOrRevert } from "../util/approveOrRevert.sol"; +import { ISmartRouter } from "../third-party/pancakeswap-v8/ISmartRouter.sol"; +import { Path } from "../third-party/pancakeswap-v8/Path.sol"; +import { PoolAddress } from "../third-party/pancakeswap-v8/PoolAddress.sol"; +import { MIN_SQRT_RATIO, MAX_SQRT_RATIO } from "../third-party/pancakeswap-v8/constants.sol"; + +/// @title ExactOutputFlashSwap +/// @notice A base contract for exact output flash swap operations. +/// +/// Upon calling _flashSwap, swaps tokenX to tokenY using a flash swap, i.e. the contract: +/// +/// 1. Invokes the flash swap on the first pool from the path +/// 2. Receives tokenY from the pool +/// 3. Calls _onMoneyReceived, which should ensure that the contract has enough tokenX +/// to repay the flash swap +/// 4. Repays the flash swap with tokenX (doing the conversion if necessary) +/// 5. Calls _onFlashSwapCompleted +/// +/// @dev This contract is abstract and should be inherited by a contract that implements +/// _onMoneyReceived and _onFlashSwapCompleted. Note that in the callbacks transaction +/// context (sender and value) is different from the original context. The inheriting +/// contracts should save the original context in the application-specific data bytes +/// passed to the callbacks. +abstract contract ExactOutputFlashSwap is IPancakeV3SwapCallback { + using SafeERC20 for IERC20; + using Path for bytes; + + /// @notice Flash swap parameters + struct FlashSwapParams { + /// @notice Amount of tokenY to receive during the flash swap + uint256 amountOut; + /// @notice Exact-output (reversed) swap path, starting with tokenY and ending with tokenX + bytes path; + /// @notice Application-specific data + bytes data; + } + + /// @notice Callback data passed to the swap callback + struct Envelope { + /// @notice Exact-output (reversed) swap path, starting with tokenY and ending with tokenX + bytes path; + /// @notice Application-specific data + bytes data; + /// @notice Pool key of the pool that should have called the callback + PoolAddress.PoolKey poolKey; + } + + /// @notice The PancakeSwap SmartRouter contract + ISmartRouter public immutable SWAP_ROUTER; + + /// @notice The PancakeSwap deployer contract + address public immutable DEPLOYER; + + /// @notice Thrown if swap callback is called by a non-PancakeSwap contract + /// @param expected Expected callback sender (pool address computed based on the pool key) + /// @param actual Actual callback sender + error InvalidCallbackSender(address expected, address actual); + + /// @notice Thrown if the swap callback is called with unexpected or zero amount of tokens + error EmptySwap(); + + /// @param swapRouter_ PancakeSwap SmartRouter contract + constructor(ISmartRouter swapRouter_) { + ensureNonzeroAddress(address(swapRouter_)); + + SWAP_ROUTER = swapRouter_; + DEPLOYER = swapRouter_.deployer(); + } + + /// @notice Callback called by PancakeSwap pool during flash swap conversion + /// @param amount0Delta Amount of pool's token0 to repay for the flash swap (negative if no need to repay this token) + /// @param amount1Delta Amount of pool's token1 to repay for the flash swap (negative if no need to repay this token) + /// @param data Callback data containing an Envelope structure + function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { + Envelope memory envelope = abi.decode(data, (Envelope)); + _verifyCallback(envelope.poolKey); + if (amount0Delta <= 0 && amount1Delta <= 0) { + revert EmptySwap(); + } + + uint256 amountToPay; + IERC20 tokenToPay; + if (amount0Delta > 0) { + tokenToPay = IERC20(envelope.poolKey.token0); + amountToPay = uint256(amount0Delta); + } else if (amount1Delta > 0) { + tokenToPay = IERC20(envelope.poolKey.token1); + amountToPay = uint256(amount1Delta); + } + + (IERC20 tokenIn, uint256 maxAmountIn) = _onMoneyReceived(envelope.data); + + if (envelope.path.hasMultiplePools()) { + bytes memory remainingPath = envelope.path.skipToken(); + approveOrRevert(tokenIn, address(SWAP_ROUTER), maxAmountIn); + SWAP_ROUTER.exactOutput( + ISmartRouter.ExactOutputParams({ + path: remainingPath, + recipient: msg.sender, // repaying to the pool + amountOut: amountToPay, + amountInMaximum: maxAmountIn + }) + ); + approveOrRevert(tokenIn, address(SWAP_ROUTER), 0); + } else { + // If the path had just one pool, tokenToPay should be tokenX, so we can just repay the debt. + tokenToPay.safeTransfer(msg.sender, amountToPay); + } + + _onFlashSwapCompleted(envelope.data); + } + + /// @dev Initiates a flash swap + /// @param params Flash swap parameters + function _flashSwap(FlashSwapParams memory params) internal { + (address tokenY, address tokenB, uint24 fee) = params.path.decodeFirstPool(); + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey(tokenY, tokenB, fee); + IPancakeV3Pool pool = IPancakeV3Pool(PoolAddress.computeAddress(DEPLOYER, poolKey)); + + bool swapZeroForOne = poolKey.token1 == tokenY; + uint160 sqrtPriceLimitX96 = (swapZeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1); + pool.swap( + address(this), + swapZeroForOne, + -int256(params.amountOut), + sqrtPriceLimitX96, + abi.encode(Envelope(params.path, params.data, poolKey)) + ); + } + + /// @dev Called when token Y is received during a flash swap. This function has to ensure + /// that at the end of the execution the contract has enough token X to repay the flash + /// swap. + /// Note that msg.sender is the pool that called the callback, not the original caller + /// of the transaction where _flashSwap was invoked. + /// @param data Application-specific data + /// @return tokenIn Token X + /// @return maxAmountIn Maximum amount of token X to be used to repay the flash swap + function _onMoneyReceived(bytes memory data) internal virtual returns (IERC20 tokenIn, uint256 maxAmountIn); + + /// @dev Called when the flash swap is completed and was paid for. By default, does nothing. + /// Note that msg.sender is the pool that called the callback, not the original caller + /// of the transaction where _flashSwap was invoked. + /// @param data Application-specific data + // solhint-disable-next-line no-empty-blocks + function _onFlashSwapCompleted(bytes memory data) internal virtual {} + + /// @dev Ensures that the caller of a callback is a legitimate PancakeSwap pool + /// @param poolKey The pool key of the pool to verify + function _verifyCallback(PoolAddress.PoolKey memory poolKey) internal view { + address pool = PoolAddress.computeAddress(DEPLOYER, poolKey); + if (msg.sender != pool) { + revert InvalidCallbackSender(pool, msg.sender); + } + } +} diff --git a/contracts/operators/TokenConverterOperator.sol b/contracts/operators/TokenConverterOperator.sol new file mode 100644 index 00000000..d5afb6de --- /dev/null +++ b/contracts/operators/TokenConverterOperator.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import { IPancakeV3SwapCallback } from "@pancakeswap/v3-core/contracts/interfaces/callback/IPancakeV3SwapCallback.sol"; +import { IPancakeV3Pool } from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; +import { IAbstractTokenConverter } from "@venusprotocol/protocol-reserve/contracts/TokenConverter/IAbstractTokenConverter.sol"; + +import { ExactOutputFlashSwap } from "../flash-swap/ExactOutputFlashSwap.sol"; +import { approveOrRevert } from "../util/approveOrRevert.sol"; +import { transferAll } from "../util/transferAll.sol"; +import { ISmartRouter } from "../third-party/pancakeswap-v8/ISmartRouter.sol"; +import { BytesLib } from "../third-party/pancakeswap-v8/BytesLib.sol"; + +/// @title TokenConverterOperator +/// @notice Converts tokens in a TokenConverter using an exact-output flash swap +/// @dev Expects a reversed (exact output) path, i.e. the path starting with the token +/// that it _sends_ to TokenConverter and ends with the token that it _receives_ from +/// TokenConverter, e.g. if TokenConverter has BTC and wants USDT, the path should be +/// USDT->(TokenB)->(TokenC)->...->BTC. This contract will then: +/// 1. Compute the amount of USDT required for the conversion +/// 2. Flash-swap TokenB to USDT (`tokenToSendToConverter`) +/// 3. Use TokenConverter to convert USDT to BTC (`tokenToReceiveFromConverter`) +/// 4. Swap some portion of BTC to an exact amount of TokenB (`tokenToPay`) +/// 5. Repay for the swap in TokenB +/// 6. Transfer the rest of BTC to the caller +/// The exact output converter differs from an exact input version in that it sends the +/// income in `tokenToReceiveFromConverter` to the beneficiary, while an exact input +/// version would send the income in `tokenToSendToConverter`. The former is supposedly +/// a bit more efficient since there's no slippage associated with the income conversion. +contract TokenConverterOperator is ExactOutputFlashSwap { + using SafeERC20 for IERC20; + using BytesLib for bytes; + + /// @notice Conversion parameters + struct ConversionParameters { + /// @notice The receiver of the arbitrage income + address beneficiary; + /// @notice The token currently in the TokenConverter + IERC20 tokenToReceiveFromConverter; + /// @notice The amount (in `tokenToReceiveFromConverter` tokens) to receive as a result of conversion + uint256 amount; + /// @notice Minimal income to get from the arbitrage transaction (in `tokenToReceiveFromConverter`). + /// This value can be negative to indicate that the sender is willing to pay for the transaction + /// execution. In this case, abs(minIncome) will be withdrawn from the sender's wallet, the + /// arbitrage will be executed, and the excess (if any) will be sent to the beneficiary. + int256 minIncome; + /// @notice The token the TokenConverter would get + IERC20 tokenToSendToConverter; + /// @notice Address of the token converter contract to arbitrage + IAbstractTokenConverter converter; + /// @notice Reversed (exact output) path to trade from `tokenToReceiveFromConverter` + /// to `tokenToSendToConverter` + bytes path; + /// @notice Deadline for the transaction execution + uint256 deadline; + } + + /// @notice Conversion data to pass between calls + struct ConversionData { + /// @notice The receiver of the arbitrage income + address beneficiary; + /// @notice The token the TokenConverter would receive + IERC20 tokenToSendToConverter; + /// @notice The amount (in `amountToSendToConverter` tokens) to send to converter + uint256 amountToSendToConverter; + /// @notice The token currently in the TokenConverter + IERC20 tokenToReceiveFromConverter; + /// @notice The amount (in `tokenToReceiveFromConverter` tokens) to receive + uint256 amountToReceiveFromConverter; + /// @notice Minimal income to get from the arbitrage transaction (in `amountToReceiveFromConverter`). + int256 minIncome; + /// @notice Address of the token converter contract to arbitrage + IAbstractTokenConverter converter; + } + + /// @notice Thrown if the provided swap path start does not correspond to tokenToSendToConverter + /// @param expected Expected swap path start (tokenToSendToConverter) + /// @param actual Provided swap path start + error InvalidSwapStart(address expected, address actual); + + /// @notice Thrown if the provided swap path end does not correspond to tokenToReceiveFromConverter + /// @param expected Expected swap path end (tokenToReceiveFromConverter) + /// @param actual Provided swap path end + error InvalidSwapEnd(address expected, address actual); + + /// @notice Thrown if the amount of to receive from TokenConverter is less than expected + /// @param expected Expected amount of tokens + /// @param actual Actual amount of tokens + error InsufficientLiquidity(uint256 expected, uint256 actual); + + /// @notice Thrown if the deadline has passed + error DeadlinePassed(uint256 currentTimestamp, uint256 deadline); + + /// @notice Thrown on math underflow + error Underflow(); + + /// @notice Thrown on math overflow + error Overflow(); + + /// @param swapRouter_ PancakeSwap SmartRouter contract + // solhint-disable-next-line no-empty-blocks + constructor(ISmartRouter swapRouter_) ExactOutputFlashSwap(swapRouter_) {} + + /// @notice Converts tokens in a TokenConverter using a flash swap + /// @param params Conversion parameters + function convert(ConversionParameters calldata params) external { + if (params.deadline < block.timestamp) { + revert DeadlinePassed(block.timestamp, params.deadline); + } + + _validatePath(params.path, address(params.tokenToSendToConverter), address(params.tokenToReceiveFromConverter)); + + (uint256 amountToReceive, uint256 amountToPay) = params.converter.getUpdatedAmountIn( + params.amount, + address(params.tokenToSendToConverter), + address(params.tokenToReceiveFromConverter) + ); + if (params.amount != amountToReceive) { + revert InsufficientLiquidity(params.amount, amountToReceive); + } + + if (params.minIncome < 0) { + params.tokenToReceiveFromConverter.safeTransferFrom(msg.sender, address(this), _u(-params.minIncome)); + } + + ConversionData memory data = ConversionData({ + beneficiary: params.beneficiary, + tokenToSendToConverter: params.tokenToSendToConverter, + amountToSendToConverter: amountToPay, + tokenToReceiveFromConverter: params.tokenToReceiveFromConverter, + amountToReceiveFromConverter: amountToReceive, + minIncome: params.minIncome, + converter: params.converter + }); + + _flashSwap(FlashSwapParams({ amountOut: amountToPay, path: params.path, data: abi.encode(data) })); + } + + function _validatePath(bytes calldata path, address expectedPathStart, address expectedPathEnd) internal pure { + address swapStart = path.toAddress(0); + if (swapStart != expectedPathStart) { + revert InvalidSwapStart(expectedPathStart, swapStart); + } + + address swapEnd = path.toAddress(path.length - 20); + if (swapEnd != expectedPathEnd) { + revert InvalidSwapEnd(expectedPathEnd, swapEnd); + } + } + + function _onMoneyReceived(bytes memory data) internal override returns (IERC20 tokenIn, uint256 maxAmountIn) { + ConversionData memory decoded = abi.decode(data, (ConversionData)); + + uint256 receivedAmount = _convertViaTokenConverter( + decoded.converter, + decoded.tokenToSendToConverter, + decoded.tokenToReceiveFromConverter, + decoded.amountToReceiveFromConverter + ); + + return (decoded.tokenToReceiveFromConverter, _u(_i(receivedAmount) - decoded.minIncome)); + } + + function _onFlashSwapCompleted(bytes memory data) internal override { + ConversionData memory decoded = abi.decode(data, (ConversionData)); + transferAll(decoded.tokenToReceiveFromConverter, address(this), decoded.beneficiary); + } + + /// @dev Get `tokenToReceive` from TokenConverter, paying with `tokenToPay` + /// @param converter TokenConverter contract + /// @param tokenToPay Token to be sent to TokenConverter + /// @param tokenToReceive Token to be received from TokenConverter + /// @param amountToReceive Amount to receive from TokenConverter in `tokenToReceive` tokens + function _convertViaTokenConverter( + IAbstractTokenConverter converter, + IERC20 tokenToPay, + IERC20 tokenToReceive, + uint256 amountToReceive + ) internal returns (uint256) { + uint256 balanceBefore = tokenToReceive.balanceOf(address(this)); + uint256 maxAmountToPay = tokenToPay.balanceOf(address(this)); + approveOrRevert(tokenToPay, address(converter), maxAmountToPay); + converter.convertForExactTokens( + maxAmountToPay, + amountToReceive, + address(tokenToPay), + address(tokenToReceive), + address(this) + ); + approveOrRevert(tokenToPay, address(converter), 0); + uint256 tokensReceived = tokenToReceive.balanceOf(address(this)) - balanceBefore; + return tokensReceived; + } + + function _u(int256 value) private pure returns (uint256) { + if (value < 0) { + revert Underflow(); + } + return uint256(value); + } + + function _i(uint256 value) private pure returns (int256) { + if (value > uint256(type(int256).max)) { + revert Overflow(); + } + return int256(value); + } +} diff --git a/contracts/third-party/pancakeswap-v8/BytesLib.sol b/contracts/third-party/pancakeswap-v8/BytesLib.sol new file mode 100644 index 00000000..899e20dd --- /dev/null +++ b/contracts/third-party/pancakeswap-v8/BytesLib.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity >=0.8.0 <0.9.0; + +library BytesLib { + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { + require(_start + 3 >= _start, "toUint24_overflow"); + require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } +} diff --git a/contracts/third-party/pancakeswap-v8/ISmartRouter.sol b/contracts/third-party/pancakeswap-v8/ISmartRouter.sol new file mode 100644 index 00000000..a41ef89a --- /dev/null +++ b/contracts/third-party/pancakeswap-v8/ISmartRouter.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0; + +interface ISmartRouter { + struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + } + + struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + } + + function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); + + function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); + + function deployer() external view returns (address); + + function WETH9() external view returns (address); +} diff --git a/contracts/third-party/pancakeswap-v8/Path.sol b/contracts/third-party/pancakeswap-v8/Path.sol new file mode 100644 index 00000000..5e58e8f4 --- /dev/null +++ b/contracts/third-party/pancakeswap-v8/Path.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.6.0; + +import "./BytesLib.sol"; + +/// @title Functions for manipulating path data for multihop swaps +library Path { + using BytesLib for bytes; + + /// @dev The length of the bytes encoded address + uint256 private constant ADDR_SIZE = 20; + /// @dev The length of the bytes encoded fee + uint256 private constant FEE_SIZE = 3; + + /// @dev The offset of a single token address and pool fee + uint256 private constant NEXT_OFFSET = ADDR_SIZE + FEE_SIZE; + /// @dev The offset of an encoded pool key + uint256 private constant POP_OFFSET = NEXT_OFFSET + ADDR_SIZE; + /// @dev The minimum length of an encoding that contains 2 or more pools + uint256 private constant MULTIPLE_POOLS_MIN_LENGTH = POP_OFFSET + NEXT_OFFSET; + + /// @notice Returns true iff the path contains two or more pools + /// @param path The encoded swap path + /// @return True if path contains two or more pools, otherwise false + function hasMultiplePools(bytes memory path) internal pure returns (bool) { + return path.length >= MULTIPLE_POOLS_MIN_LENGTH; + } + + /// @notice Returns the number of pools in the path + /// @param path The encoded swap path + /// @return The number of pools in the path + function numPools(bytes memory path) internal pure returns (uint256) { + // Ignore the first token address. From then on every fee and token offset indicates a pool. + return ((path.length - ADDR_SIZE) / NEXT_OFFSET); + } + + /// @notice Decodes the first pool in path + /// @param path The bytes encoded swap path + /// @return tokenA The first token of the given pool + /// @return tokenB The second token of the given pool + /// @return fee The fee level of the pool + function decodeFirstPool(bytes memory path) internal pure returns (address tokenA, address tokenB, uint24 fee) { + tokenA = path.toAddress(0); + fee = path.toUint24(ADDR_SIZE); + tokenB = path.toAddress(NEXT_OFFSET); + } + + /// @notice Gets the segment corresponding to the first pool in the path + /// @param path The bytes encoded swap path + /// @return The segment containing all data necessary to target the first pool in the path + function getFirstPool(bytes memory path) internal pure returns (bytes memory) { + return path.slice(0, POP_OFFSET); + } + + /// @notice Skips a token + fee element from the buffer and returns the remainder + /// @param path The swap path + /// @return The remaining token + fee elements in the path + function skipToken(bytes memory path) internal pure returns (bytes memory) { + return path.slice(NEXT_OFFSET, path.length - NEXT_OFFSET); + } +} diff --git a/contracts/third-party/pancakeswap-v8/PoolAddress.sol b/contracts/third-party/pancakeswap-v8/PoolAddress.sol new file mode 100644 index 00000000..16097f98 --- /dev/null +++ b/contracts/third-party/pancakeswap-v8/PoolAddress.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee +library PoolAddress { + // The following line was modified by Venus so that the init hash corresponds to the one of PancakeSwap + bytes32 internal constant POOL_INIT_CODE_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2; + + /// @notice The identifying key of the pool + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param tokenA The first token of a pool, unsorted + /// @param tokenB The second token of a pool, unsorted + /// @param fee The fee level of the pool + /// @return Poolkey The pool details with ordered token0 and token1 assignments + function getPoolKey(address tokenA, address tokenB, uint24 fee) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({ token0: tokenA, token1: tokenB, fee: fee }); + } + + /// @notice Deterministically computes the pool address given the factory and PoolKey + /// @param factory The Uniswap V3 factory contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { + require(key.token0 < key.token1); + pool = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encode(key.token0, key.token1, key.fee)), + POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} diff --git a/contracts/third-party/pancakeswap-v8/constants.sol b/contracts/third-party/pancakeswap-v8/constants.sol new file mode 100644 index 00000000..c0980958 --- /dev/null +++ b/contracts/third-party/pancakeswap-v8/constants.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.5.0; + +/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) +uint160 constant MIN_SQRT_RATIO = 4295128739; +/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) +uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; diff --git a/contracts/util/approveOrRevert.sol b/contracts/util/approveOrRevert.sol new file mode 100644 index 00000000..02963a23 --- /dev/null +++ b/contracts/util/approveOrRevert.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @notice Thrown if a contract is unable to approve a transfer +error ApproveFailed(); + +/// @notice Approves a transfer, ensuring that it is successful. This function supports non-compliant +/// tokens like the ones that don't return a boolean value on success. Thus, such approve call supports +/// three different kinds of tokens: +/// * Compliant tokens that revert on failure +/// * Compliant tokens that return false on failure +/// * Non-compliant tokens that don't return a value +/// @param token The contract address of the token which will be transferred +/// @param spender The spender contract address +/// @param amount The value of the transfer +function approveOrRevert(IERC20 token, address spender, uint256 amount) { + bytes memory callData = abi.encodeCall(token.approve, (spender, amount)); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = address(token).call(callData); + + if (!success || (result.length != 0 && !abi.decode(result, (bool)))) { + revert ApproveFailed(); + } +} diff --git a/contracts/util/transferAll.sol b/contracts/util/transferAll.sol new file mode 100644 index 00000000..29a87b90 --- /dev/null +++ b/contracts/util/transferAll.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +using SafeERC20 for IERC20; + +function transferAll(IERC20 token, address this_, address to) { + uint256 balance = token.balanceOf(this_); + if (balance > 0) { + token.safeTransfer(to, balance); + } +} diff --git a/package.json b/package.json index ca0a29e8..c281e738 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,9 @@ }, "dependencies": { "@openzeppelin/contracts-upgradeable": "4.9.3", - "@venusprotocol/protocol-reserve": "1.0.0-converters.1", - "@venusprotocol/venus-protocol": "6.1.0-dev.8" + "@venusprotocol/governance-contracts": "^1.4.0", + "@venusprotocol/protocol-reserve": "^1.4.0", + "@venusprotocol/venus-protocol": "^7.1.0", + "viem": "^2.1.1" } } diff --git a/src/config/abis/AbstractTokenConverter.ts b/src/config/abis/AbstractTokenConverter.ts new file mode 100644 index 00000000..0a8ff985 --- /dev/null +++ b/src/config/abis/AbstractTokenConverter.ts @@ -0,0 +1,1272 @@ +// @kkirka: This is a typescript file and not a JSON file because I wanted to keep +// the narrow type for viem (importing from JSON yields an array of garbage). I guess +// we could generate these files similarly to how it's done in the frontend repo +// (in the postinstall script), but I'd keep this up for discussion for now. + +export default [ + { + inputs: [ + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountInMaxMantissa", + type: "uint256", + }, + ], + name: "AmountInHigherThanMax", + type: "error", + }, + { + inputs: [], + name: "AmountInMismatched", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMinMantissa", + type: "uint256", + }, + ], + name: "AmountOutLowerThanMinRequired", + type: "error", + }, + { + inputs: [], + name: "AmountOutMismatched", + type: "error", + }, + { + inputs: [], + name: "ConversionConfigNotEnabled", + type: "error", + }, + { + inputs: [], + name: "ConversionEnabledOnlyForPrivateConversions", + type: "error", + }, + { + inputs: [], + name: "ConversionTokensActive", + type: "error", + }, + { + inputs: [], + name: "ConversionTokensPaused", + type: "error", + }, + { + inputs: [], + name: "DeflationaryTokenNotSupported", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "incentive", + type: "uint256", + }, + { + internalType: "uint256", + name: "maxIncentive", + type: "uint256", + }, + ], + name: "IncentiveTooHigh", + type: "error", + }, + { + inputs: [], + name: "InputLengthMisMatch", + type: "error", + }, + { + inputs: [], + name: "InsufficientInputAmount", + type: "error", + }, + { + inputs: [], + name: "InsufficientOutputAmount", + type: "error", + }, + { + inputs: [], + name: "InsufficientPoolLiquidity", + type: "error", + }, + { + inputs: [], + name: "InvalidConverterNetwork", + type: "error", + }, + { + inputs: [], + name: "InvalidMinimumAmountToConvert", + type: "error", + }, + { + inputs: [], + name: "InvalidToAddress", + type: "error", + }, + { + inputs: [], + name: "InvalidTokenConfigAddresses", + type: "error", + }, + { + inputs: [], + name: "NonZeroIncentiveForPrivateConversion", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "address", + name: "calledContract", + type: "address", + }, + { + internalType: "string", + name: "methodSignature", + type: "string", + }, + ], + name: "Unauthorized", + type: "error", + }, + { + inputs: [], + name: "ZeroAddressNotAllowed", + type: "error", + }, + { + inputs: [], + name: "ZeroValueNotAllowed", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "oldIncentive", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "newIncentive", + type: "uint256", + }, + { + indexed: false, + internalType: "enum IAbstractTokenConverter.ConversionAccessibility", + name: "oldAccess", + type: "uint8", + }, + { + indexed: false, + internalType: "enum IAbstractTokenConverter.ConversionAccessibility", + name: "newAccess", + type: "uint8", + }, + ], + name: "ConversionConfigUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "ConversionPaused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "ConversionResumed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "receiver", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amountIn", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "amountOut", + type: "uint256", + }, + ], + name: "ConvertedExactTokens", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "receiver", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amountIn", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "amountOut", + type: "uint256", + }, + ], + name: "ConvertedExactTokensSupportingFeeOnTransferTokens", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "receiver", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amountIn", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "amountOut", + type: "uint256", + }, + ], + name: "ConvertedForExactTokens", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "receiver", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amountIn", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "amountOut", + type: "uint256", + }, + ], + name: "ConvertedForExactTokensSupportingFeeOnTransferTokens", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldConverterNetwork", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "converterNetwork", + type: "address", + }, + ], + name: "ConverterNetworkAddressUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldDestinationAddress", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "destinationAddress", + type: "address", + }, + ], + name: "DestinationAddressUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "oldMinAmountToConvert", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "newMinAmountToConvert", + type: "uint256", + }, + ], + name: "MinAmountToConvertUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "oldAccessControlManager", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newAccessControlManager", + type: "address", + }, + ], + name: "NewAccessControlManager", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferStarted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ResilientOracle", + name: "oldPriceOracle", + type: "address", + }, + { + indexed: true, + internalType: "contract ResilientOracle", + name: "priceOracle", + type: "address", + }, + ], + name: "PriceOracleUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "token", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "SweepToken", + type: "event", + }, + { + inputs: [], + name: "MAX_INCENTIVE", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "acceptOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "accessControlManager", + outputs: [ + { + internalType: "contract IAccessControlManagerV8", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "tokenBalance", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "conversionConfigurations", + outputs: [ + { + internalType: "uint256", + name: "incentive", + type: "uint256", + }, + { + internalType: "enum IAbstractTokenConverter.ConversionAccessibility", + name: "conversionAccess", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "conversionPaused", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMinMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "convertExactTokens", + outputs: [ + { + internalType: "uint256", + name: "actualAmountIn", + type: "uint256", + }, + { + internalType: "uint256", + name: "actualAmountOut", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMinMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "convertExactTokensSupportingFeeOnTransferTokens", + outputs: [ + { + internalType: "uint256", + name: "actualAmountIn", + type: "uint256", + }, + { + internalType: "uint256", + name: "actualAmountOut", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMaxMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "convertForExactTokens", + outputs: [ + { + internalType: "uint256", + name: "actualAmountIn", + type: "uint256", + }, + { + internalType: "uint256", + name: "actualAmountOut", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMaxMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "convertForExactTokensSupportingFeeOnTransferTokens", + outputs: [ + { + internalType: "uint256", + name: "actualAmountIn", + type: "uint256", + }, + { + internalType: "uint256", + name: "actualAmountOut", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "converterNetwork", + outputs: [ + { + internalType: "contract IConverterNetwork", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "destinationAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + ], + name: "getAmountIn", + outputs: [ + { + internalType: "uint256", + name: "amountConvertedMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + ], + name: "getAmountOut", + outputs: [ + { + internalType: "uint256", + name: "amountConvertedMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + ], + name: "getUpdatedAmountIn", + outputs: [ + { + internalType: "uint256", + name: "amountConvertedMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountInMantissa", + type: "uint256", + }, + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + ], + name: "getUpdatedAmountOut", + outputs: [ + { + internalType: "uint256", + name: "amountConvertedMantissa", + type: "uint256", + }, + { + internalType: "uint256", + name: "amountOutMantissa", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "minAmountToConvert", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pauseConversion", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "priceOracle", + outputs: [ + { + internalType: "contract ResilientOracle", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "resumeConversion", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "accessControlManager_", + type: "address", + }, + ], + name: "setAccessControlManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address", + name: "tokenAddressOut", + type: "address", + }, + { + components: [ + { + internalType: "uint256", + name: "incentive", + type: "uint256", + }, + { + internalType: "enum IAbstractTokenConverter.ConversionAccessibility", + name: "conversionAccess", + type: "uint8", + }, + ], + internalType: "struct IAbstractTokenConverter.ConversionConfig", + name: "conversionConfig", + type: "tuple", + }, + ], + name: "setConversionConfig", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "tokenAddressIn", + type: "address", + }, + { + internalType: "address[]", + name: "tokenAddressesOut", + type: "address[]", + }, + { + components: [ + { + internalType: "uint256", + name: "incentive", + type: "uint256", + }, + { + internalType: "enum IAbstractTokenConverter.ConversionAccessibility", + name: "conversionAccess", + type: "uint8", + }, + ], + internalType: "struct IAbstractTokenConverter.ConversionConfig[]", + name: "conversionConfigs", + type: "tuple[]", + }, + ], + name: "setConversionConfigs", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IConverterNetwork", + name: "converterNetwork_", + type: "address", + }, + ], + name: "setConverterNetwork", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "destinationAddress_", + type: "address", + }, + ], + name: "setDestination", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "minAmountToConvert_", + type: "uint256", + }, + ], + name: "setMinAmountToConvert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ResilientOracle", + name: "priceOracle_", + type: "address", + }, + ], + name: "setPriceOracle", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "tokenAddress", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "sweepToken", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "comptroller", + type: "address", + }, + { + internalType: "address", + name: "asset", + type: "address", + }, + ], + name: "updateAssetsState", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/src/config/abis/TokenConverterOperator.ts b/src/config/abis/TokenConverterOperator.ts new file mode 100644 index 00000000..a51f5644 --- /dev/null +++ b/src/config/abis/TokenConverterOperator.ts @@ -0,0 +1,227 @@ +// @kkirka: This is a typescript file and not a JSON file because I wanted to keep +// the narrow type for viem (importing from JSON yields an array of garbage). I guess +// we could generate these files similarly to how it's done in the frontend repo +// (in the postinstall script), but I'd keep this up for discussion for now. + +export default [ + { + inputs: [ + { + internalType: "contract ISmartRouter", + name: "swapRouter_", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ApproveFailed", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "currentTimestamp", + type: "uint256", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + ], + name: "DeadlinePassed", + type: "error", + }, + { + inputs: [], + name: "EmptySwap", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "expected", + type: "uint256", + }, + { + internalType: "uint256", + name: "actual", + type: "uint256", + }, + ], + name: "InsufficientLiquidity", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "expected", + type: "address", + }, + { + internalType: "address", + name: "actual", + type: "address", + }, + ], + name: "InvalidCallbackSender", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "expected", + type: "address", + }, + { + internalType: "address", + name: "actual", + type: "address", + }, + ], + name: "InvalidSwapEnd", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "expected", + type: "address", + }, + { + internalType: "address", + name: "actual", + type: "address", + }, + ], + name: "InvalidSwapStart", + type: "error", + }, + { + inputs: [], + name: "Overflow", + type: "error", + }, + { + inputs: [], + name: "Underflow", + type: "error", + }, + { + inputs: [], + name: "ZeroAddressNotAllowed", + type: "error", + }, + { + inputs: [], + name: "DEPLOYER", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SWAP_ROUTER", + outputs: [ + { + internalType: "contract ISmartRouter", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "beneficiary", + type: "address", + }, + { + internalType: "contract IERC20", + name: "tokenToReceiveFromConverter", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "int256", + name: "minIncome", + type: "int256", + }, + { + internalType: "contract IERC20", + name: "tokenToSendToConverter", + type: "address", + }, + { + internalType: "contract IAbstractTokenConverter", + name: "converter", + type: "address", + }, + { + internalType: "bytes", + name: "path", + type: "bytes", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + ], + internalType: "struct TokenConverterOperator.ConversionParameters", + name: "params", + type: "tuple", + }, + ], + name: "convert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "int256", + name: "amount0Delta", + type: "int256", + }, + { + internalType: "int256", + name: "amount1Delta", + type: "int256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "pancakeV3SwapCallback", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/src/config/addresses.ts b/src/config/addresses.ts new file mode 100644 index 00000000..9499184e --- /dev/null +++ b/src/config/addresses.ts @@ -0,0 +1,35 @@ +import bscmainnetGovernance from "@venusprotocol/governance-contracts/deployments/bscmainnet_addresses.json"; +import bsctestnetGovernance from "@venusprotocol/governance-contracts/deployments/bsctestnet_addresses.json"; +import bscmainnetProtocolReserve from "@venusprotocol/protocol-reserve/deployments/bscmainnet_addresses.json"; +import bsctestnetProtocolReserve from "@venusprotocol/protocol-reserve/deployments/bsctestnet_addresses.json"; +import bscmainnetCore from "@venusprotocol/venus-protocol/deployments/bscmainnet_addresses.json"; +import bsctestnetCore from "@venusprotocol/venus-protocol/deployments/bsctestnet_addresses.json"; + +export const addresses = { + bscmainnet: { + ...bscmainnetCore.addresses, + ...bscmainnetProtocolReserve.addresses, + ...bscmainnetGovernance.addresses, + BUSD: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", + USDT: "0x55d398326f99059fF775485246999027B3197955", + USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + PancakeSwapRouter: "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4", + }, + bsctestnet: { + ...bsctestnetCore.addresses, + ...bsctestnetProtocolReserve.addresses, + ...bsctestnetGovernance.addresses, + PancakeSwapRouter: "0x1b81D678ffb9C0263b24A97847620C99d213eB14", + xvsHolder: "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706", + usdtHolder: "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706", + + TokenConverterOperator: "0x9222F8b71603318d5EEbBf0074c2Da07fEbbB9eb", + }, +} as const; + +type Addresses = typeof addresses; + +export type HasAddressFor = { + [ChainT in keyof Addresses]: Addresses[ChainT] extends Record ? ChainT : never; +}[keyof Addresses]; diff --git a/src/config/chains.ts b/src/config/chains.ts new file mode 100644 index 00000000..9a7edfe6 --- /dev/null +++ b/src/config/chains.ts @@ -0,0 +1,6 @@ +import { bsc, bscTestnet } from "viem/chains"; + +export const chains = { + bscmainnet: bsc, + bsctestnet: bscTestnet, +} as const; diff --git a/src/config/clients.ts b/src/config/clients.ts new file mode 100644 index 00000000..aac88738 --- /dev/null +++ b/src/config/clients.ts @@ -0,0 +1,31 @@ +import { HttpTransport, PublicClient, WalletClient, createPublicClient, createWalletClient, http } from "viem"; +import { PrivateKeyAccount, privateKeyToAccount } from "viem/accounts"; + +import { chains } from "./chains"; + +export const getPublicClient = ( + chainName: ChainT, +): PublicClient => { + return createPublicClient({ + chain: chains[chainName], + transport: http(process.env[`LIVE_NETWORK_${chainName}`]), + }); +}; + +const readPrivateKeyFromEnv = (chainName: string): PrivateKeyAccount => { + const key = process.env[`PRIVATE_KEY_${chainName}`]; + if (key?.startsWith("0x")) { + return privateKeyToAccount(key as `0x${string}`); + } + throw new Error(`Invalid private key for ${chainName}. Please specify PRIVATE_KEY_${chainName} env variable.`); +}; + +export const getWalletClient = ( + chainName: ChainT, +): WalletClient => { + return createWalletClient({ + chain: chains[chainName], + transport: http(process.env[`LIVE_NETWORK_${chainName}`]), + account: readPrivateKeyFromEnv(chainName), + }); +}; diff --git a/src/converter-bot/path.ts b/src/converter-bot/path.ts new file mode 100644 index 00000000..693cfd7a --- /dev/null +++ b/src/converter-bot/path.ts @@ -0,0 +1,35 @@ +import { Address, Hex, concat, numberToHex } from "viem"; + +const ensureAddress = (addressOrNumber: Address | number): Address => { + if (typeof addressOrNumber === "number") { + throw new Error(`Invalid address: ${addressOrNumber}`); + } + return addressOrNumber; +}; + +const ensureNumber = (addressOrNumber: Address | number): number => { + if (typeof addressOrNumber !== "number") { + throw new Error(`Invalid number: ${addressOrNumber}`); + } + return addressOrNumber; +}; + +export interface Path { + readonly start: Address; + readonly end: Address; + readonly data: ReadonlyArray
; + readonly hex: Hex; +} + +export const parsePath = (data: Array
): Path => { + const start = ensureAddress(data[0]); + const end = ensureAddress(data[data.length - 1]); + let hex: Hex = "0x"; + for (let i = 0; i < data.length; i += 3) { + const tokenA = ensureAddress(data[i]); + const fee = numberToHex(ensureNumber(data[i + 1]), { size: 3 }); + const tokenB = ensureAddress(data[i + 2]); + hex = concat([hex, tokenA, fee, tokenB]); + } + return { start, end, data, hex }; +}; diff --git a/src/converter-bot/tokenConverterBot.ts b/src/converter-bot/tokenConverterBot.ts new file mode 100644 index 00000000..8a169668 --- /dev/null +++ b/src/converter-bot/tokenConverterBot.ts @@ -0,0 +1,116 @@ +import { Address, parseAbi, parseUnits } from "viem"; + +import TokenConverterOperator from "../config/abis/TokenConverterOperator"; +import { type HasAddressFor, addresses } from "../config/addresses"; +import { chains } from "../config/chains"; +import { getPublicClient, getWalletClient } from "../config/clients"; +import { Path, parsePath } from "./path"; + +type SupportedConverters = + | "BTCBPrimeConverter" + | "ETHPrimeConverter" + | "RiskFundConverter" + | "USDCPrimeConverter" + | "USDTPrimeConverter" + | "XVSVaultConverter"; + +type SupportedChains = HasAddressFor<"TokenConverterOperator" | SupportedConverters>; + +const REVERT_IF_NOT_MINED_AFTER = 60n; //seconds + +class Bot { + private chainName: SupportedChains; + private operator: { address: Address; abi: typeof TokenConverterOperator }; + private addresses: typeof addresses[SupportedChains]; + private _walletClient?: ReturnType>; + private _publicClient?: ReturnType>; + + constructor(chainName: SupportedChains) { + this.chainName = chainName; + this.addresses = addresses[chainName]; + this.operator = { + address: addresses[chainName].TokenConverterOperator, + abi: TokenConverterOperator, + }; + } + + get publicClient() { + return (this._publicClient ||= getPublicClient(this.chainName)); + } + + get walletClient() { + return (this._walletClient ||= getWalletClient(this.chainName)); + } + + async sanityCheck() { + const expected = this.addresses.PancakeSwapRouter; + const actual = await this.publicClient.readContract({ + ...this.operator, + functionName: "SWAP_ROUTER", + }); + if (expected !== actual) { + throw new Error(`Expected swap router to be at ${expected} but found at ${actual}`); + } + } + + async arbitrage(converter: SupportedConverters, path: Path, amount: bigint, minIncome: bigint) { + const converterAddress = this.addresses[converter]; + const beneficiary = this.walletClient.account.address; + const chain = chains[this.chainName]; + + if (minIncome < 0n) { + await this.walletClient.writeContract({ + address: path.end, + chain, + abi: parseAbi(["function approve(address,uint256)"]), + functionName: "approve", + args: [this.operator.address, -minIncome], + }); + } + + const block = await this.publicClient.getBlock(); + await this.walletClient.writeContract({ + ...this.operator, + chain, + functionName: "convert", + args: [ + { + beneficiary, + tokenToReceiveFromConverter: path.end, + amount, + minIncome, + tokenToSendToConverter: path.start, + converter: converterAddress, + path: path.hex, + deadline: block.timestamp + REVERT_IF_NOT_MINED_AFTER, + }, + ], + }); + } +} + +const main = async () => { + const bot = new Bot("bsctestnet"); + await bot.sanityCheck(); + + // Imagine the converter has LTC and wants USDT + // tokenToSendToConverter: USDT + // tokenToReceiveFromConverter: LTC + // We're swapping LTC to USDT on PCS, so + // the PCS reversed path should start with + // USDT (tokenToSendToConverter) and end + // with LTC (tokenToReceiveFromConverter) + // + // The income is paid out in LTC (if any) + await bot.arbitrage( + "RiskFundConverter", + parsePath([addresses.bsctestnet.USDT as Address, 500, addresses.bsctestnet.LTC as Address]), + parseUnits("1", 18), // 1 LTC + parseUnits("-0.1", 18), // We're ok with paying 0.1 LTC for this conversion + ); +}; + +main().catch(error => { + console.error(error); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index 071f977d..7bafef9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.0": + version: 1.10.0 + resolution: "@adraffy/ens-normalize@npm:1.10.0" + checksum: af0540f963a2632da2bbc37e36ea6593dcfc607b937857133791781e246d47f870d5e3d21fa70d5cfe94e772c284588c81ea3f5b7f4ea8fbb824369444e4dbcb + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.1.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -730,7 +737,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.6.3, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.4.0, @ethersproject/abi@npm:^5.6.3, @ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -762,7 +769,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": +"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.4.1, @ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" dependencies: @@ -775,7 +782,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.4.0, @ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -807,7 +814,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": +"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.4.1, @ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" dependencies: @@ -818,7 +825,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": +"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.4.0, @ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -827,7 +834,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": +"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.4.0, @ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" dependencies: @@ -836,7 +843,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.7.0, @ethersproject/contracts@npm:^5.7.0": +"@ethersproject/contracts@npm:5.7.0, @ethersproject/contracts@npm:^5.4.1, @ethersproject/contracts@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/contracts@npm:5.7.0" dependencies: @@ -957,7 +964,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": +"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.4.4, @ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" dependencies: @@ -1030,7 +1037,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.7.0": +"@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.4.0, @ethersproject/solidity@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/solidity@npm:5.7.0" dependencies: @@ -1055,7 +1062,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": +"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.4.0, @ethersproject/transactions@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/transactions@npm:5.7.0" dependencies: @@ -1083,7 +1090,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wallet@npm:5.7.0, @ethersproject/wallet@npm:^5.7.0": +"@ethersproject/wallet@npm:5.7.0, @ethersproject/wallet@npm:^5.4.0, @ethersproject/wallet@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/wallet@npm:5.7.0" dependencies: @@ -1230,6 +1237,36 @@ __metadata: languageName: node linkType: hard +"@layerzerolabs/lz-evm-sdk-v1-0.7@npm:^1.5.14": + version: 1.5.16 + resolution: "@layerzerolabs/lz-evm-sdk-v1-0.7@npm:1.5.16" + dependencies: + "@openzeppelin/contracts": 3.4.2-solc-0.7 + "@openzeppelin/contracts-upgradeable": 3.4.2-solc-0.7 + checksum: c4a42f446fe4c961bdfc5cbdd848b98ba1f2352ddd539d04affba7d9760905b0604a3422a1a9a7e89808466e6fcbb05e40498948b663beb21e47127b00d9cd9f + languageName: node + linkType: hard + +"@layerzerolabs/solidity-examples@npm:^1.0.0": + version: 1.1.0 + resolution: "@layerzerolabs/solidity-examples@npm:1.1.0" + dependencies: + "@layerzerolabs/lz-evm-sdk-v1-0.7": ^1.5.14 + "@openzeppelin-3/contracts": "npm:@openzeppelin/contracts@^3.4.2-solc-0.7" + "@openzeppelin/contracts": ^4.4.1 + "@openzeppelin/contracts-upgradeable": ^4.6.0 + "@openzeppelin/hardhat-upgrades": ^1.18.3 + dotenv: ^10.0.0 + erc721a: ^4.2.3 + hardhat: ^2.8.0 + hardhat-contract-sizer: ^2.1.1 + hardhat-deploy: ^0.10.5 + hardhat-deploy-ethers: ^0.3.0-beta.13 + hardhat-gas-reporter: ^1.0.6 + checksum: f7603710b18bdda227024b161059b5f725499529d25ea2c6dc7a16c664f2b25bd2be2761e81e870a08b05013c425bf7b5258e7a667ac9390cf598c043b216407 + languageName: node + linkType: hard + "@metamask/eth-sig-util@npm:^4.0.0": version: 4.0.1 resolution: "@metamask/eth-sig-util@npm:4.0.1" @@ -1252,6 +1289,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" + dependencies: + "@noble/hashes": 1.3.2 + checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 + languageName: node + linkType: hard + "@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": version: 1.2.0 resolution: "@noble/hashes@npm:1.2.0" @@ -1266,13 +1312,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": +"@noble/hashes@npm:1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": version: 1.3.2 resolution: "@noble/hashes@npm:1.3.2" checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 languageName: node linkType: hard +"@noble/hashes@npm:~1.3.2": + version: 1.3.3 + resolution: "@noble/hashes@npm:1.3.3" + checksum: 8a6496d1c0c64797339bc694ad06cdfaa0f9e56cd0c3f68ae3666cfb153a791a55deb0af9c653c7ed2db64d537aa3e3054629740d2f2338bb1dcb7ab60cd205b + languageName: node + linkType: hard + "@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" @@ -2136,6 +2189,20 @@ __metadata: languageName: node linkType: hard +"@openzeppelin-3/contracts@npm:@openzeppelin/contracts@^3.4.2-solc-0.7, @openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": + version: 3.4.2 + resolution: "@openzeppelin/contracts@npm:3.4.2" + checksum: 0c90f029fe50a49643588e4c8670dae3bbf31795133a6ddce9bdcbc258486332700bb732287baabf7bf807f39182fe8ea2ffa19aa5caf359b1b9c0f083280748 + languageName: node + linkType: hard + +"@openzeppelin/contracts-upgradeable@npm:3.4.2-solc-0.7": + version: 3.4.2-solc-0.7 + resolution: "@openzeppelin/contracts-upgradeable@npm:3.4.2-solc-0.7" + checksum: 662d168ea1763faa5e168751043e4e041ccd810c3d4c781aa5aee0e9947e9f95d51edaeb1daaa4cc2860463beb961b576c6a3e60e3fbb6fa27188a611c8522e4 + languageName: node + linkType: hard + "@openzeppelin/contracts-upgradeable@npm:4.9.3, @openzeppelin/contracts-upgradeable@npm:^4.7.3, @openzeppelin/contracts-upgradeable@npm:^4.8.0, @openzeppelin/contracts-upgradeable@npm:^4.8.3": version: 4.9.3 resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.3" @@ -2143,10 +2210,17 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": - version: 3.4.2 - resolution: "@openzeppelin/contracts@npm:3.4.2" - checksum: 0c90f029fe50a49643588e4c8670dae3bbf31795133a6ddce9bdcbc258486332700bb732287baabf7bf807f39182fe8ea2ffa19aa5caf359b1b9c0f083280748 +"@openzeppelin/contracts-upgradeable@npm:^4.6.0": + version: 4.9.5 + resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.5" + checksum: d5d9bad93fe2a88a8336060901d45f185ebfe1fed9844b1fe50e8641d946588c6e69a5a8f1694f0bd073c913b4b9a5c34316acfc17d8f75228adecbcb635ea62 + languageName: node + linkType: hard + +"@openzeppelin/contracts@npm:3.4.2-solc-0.7": + version: 3.4.2-solc-0.7 + resolution: "@openzeppelin/contracts@npm:3.4.2-solc-0.7" + checksum: 1a6048f31ed560c34429a05e534102c51124ecaf113aca7ebeb7897cfaaf61007cdd7952374c282adaeb79b04ee86ee80b16eed28b62fc6d60e3ffcd7a696895 languageName: node linkType: hard @@ -2157,6 +2231,13 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts@npm:^4.4.1": + version: 4.9.5 + resolution: "@openzeppelin/contracts@npm:4.9.5" + checksum: 2cddeb08c006a8f99c5cc40cc80aecb449fd941cd1a92ebda315d77f48c4b4d487798a1254bffbc3ec811b390365d14665e92dbb2dd8f45aacef479d69d94574 + languageName: node + linkType: hard + "@openzeppelin/defender-base-client@npm:^1.46.0": version: 1.52.0 resolution: "@openzeppelin/defender-base-client@npm:1.52.0" @@ -2170,7 +2251,7 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/hardhat-upgrades@npm:^1.21.0": +"@openzeppelin/hardhat-upgrades@npm:^1.18.3, @openzeppelin/hardhat-upgrades@npm:^1.21.0": version: 1.28.0 resolution: "@openzeppelin/hardhat-upgrades@npm:1.28.0" dependencies: @@ -2273,6 +2354,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.2": + version: 1.1.5 + resolution: "@scure/base@npm:1.1.5" + checksum: 9e9ee6088cb3aa0fb91f5a48497d26682c7829df3019b1251d088d166d7a8c0f941c68aaa8e7b96bbad20c71eb210397cb1099062cde3e29d4bad6b975c18519 + languageName: node + linkType: hard + "@scure/bip32@npm:1.1.5": version: 1.1.5 resolution: "@scure/bip32@npm:1.1.5" @@ -2295,6 +2383,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.3.2": + version: 1.3.2 + resolution: "@scure/bip32@npm:1.3.2" + dependencies: + "@noble/curves": ~1.2.0 + "@noble/hashes": ~1.3.2 + "@scure/base": ~1.1.2 + checksum: c5ae84fae43490853693b481531132b89e056d45c945fc8b92b9d032577f753dfd79c5a7bbcbf0a7f035951006ff0311b6cf7a389e26c9ec6335e42b20c53157 + languageName: node + linkType: hard + "@scure/bip39@npm:1.1.1": version: 1.1.1 resolution: "@scure/bip39@npm:1.1.1" @@ -3069,20 +3168,36 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/governance-contracts@npm:^1.4.0-dev.2": - version: 1.4.0-dev.11 - resolution: "@venusprotocol/governance-contracts@npm:1.4.0-dev.11" +"@venusprotocol/governance-contracts@npm:^1.4.0": + version: 1.4.0 + resolution: "@venusprotocol/governance-contracts@npm:1.4.0" dependencies: "@venusprotocol/solidity-utilities": ^1.1.0 hardhat-deploy-ethers: ^0.3.0-beta.13 module-alias: ^2.2.2 - checksum: 5fc841a1298f640573c6871b5ecedefa7469afe0e109f3468fdcac2d4e911efd4aa491a06f7ea10bbf0635192e41c83c42f567c834e01aa0307965b03d0ed736 + checksum: 85c6b6a815edb0befa4c38e3652a58464827d390620210b99575c16960ee6505e95e7c2192ebc972da7ed758d3c62e150d32fbdd1f01acab1731f29b11d1884e languageName: node linkType: hard -"@venusprotocol/isolated-pools@npm:^2.0.0": - version: 2.2.0 - resolution: "@venusprotocol/isolated-pools@npm:2.2.0" +"@venusprotocol/isolated-pools@npm:2.3.0": + version: 2.3.0 + resolution: "@venusprotocol/isolated-pools@npm:2.3.0" + dependencies: + "@nomiclabs/hardhat-ethers": ^2.2.3 + "@openzeppelin/contracts": ^4.8.3 + "@openzeppelin/contracts-upgradeable": ^4.8.3 + "@openzeppelin/hardhat-upgrades": ^1.21.0 + "@solidity-parser/parser": ^0.13.2 + ethers: ^5.7.0 + hardhat-deploy: ^0.11.14 + module-alias: ^2.2.2 + checksum: e3ef4115048b41415e50ff8d7c4297a93e88f7914ce7eb061d1c976d686c8c5c0082d01612a615a61ef9d130ff2a72e7acba2a6612eda0fc44e12ccb9277c411 + languageName: node + linkType: hard + +"@venusprotocol/isolated-pools@npm:^2.3.0": + version: 2.4.0 + resolution: "@venusprotocol/isolated-pools@npm:2.4.0" dependencies: "@nomiclabs/hardhat-ethers": ^2.2.3 "@openzeppelin/contracts": ^4.8.3 @@ -3092,7 +3207,7 @@ __metadata: ethers: ^5.7.0 hardhat-deploy: ^0.11.14 module-alias: ^2.2.2 - checksum: f956867ebe199724b4abceac518d2591729455c486ef76fe8a6d7f27fab73cb46883c72aff694084229f08d8c2e24e74a7b121e556a7ca75040a37e7b4126361 + checksum: c67b2d1eaf2ae7cae1e7b73799ec1dd0ffd3bc996bea09fc6e6c909f416f65fa168bd0690034ce8882da0d0b9a8825032dde3dd15bad392a5a5cad32bf697a73 languageName: node linkType: hard @@ -3122,10 +3237,11 @@ __metadata: "@types/node": ^20.10.0 "@typescript-eslint/eslint-plugin": ^6.13.1 "@typescript-eslint/parser": ^6.13.1 + "@venusprotocol/governance-contracts": ^1.4.0 "@venusprotocol/oracle": ^1.7.3 - "@venusprotocol/protocol-reserve": 1.0.0-converters.1 + "@venusprotocol/protocol-reserve": ^1.4.0 "@venusprotocol/solidity-utilities": ^1.1.0 - "@venusprotocol/venus-protocol": 6.1.0-dev.8 + "@venusprotocol/venus-protocol": ^7.1.0 chai: ^4.3.10 dotenv: ^16.3.1 eslint: ^7.32.0 @@ -3149,6 +3265,7 @@ __metadata: ts-node: ^10.9.1 typechain: ^8.3.2 typescript: ^5.3.2 + viem: ^2.1.1 languageName: unknown linkType: soft @@ -3172,38 +3289,38 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/protocol-reserve@npm:1.0.0-converters.1": - version: 1.0.0-converters.1 - resolution: "@venusprotocol/protocol-reserve@npm:1.0.0-converters.1" +"@venusprotocol/protocol-reserve@npm:^1.2.0": + version: 1.3.0 + resolution: "@venusprotocol/protocol-reserve@npm:1.3.0" dependencies: "@nomiclabs/hardhat-ethers": ^2.2.3 "@openzeppelin/contracts": ^4.8.3 "@openzeppelin/contracts-upgradeable": ^4.8.3 "@openzeppelin/hardhat-upgrades": ^1.21.0 "@solidity-parser/parser": ^0.13.2 - "@venusprotocol/isolated-pools": ^2.0.0 "@venusprotocol/solidity-utilities": ^1.0.1 ethers: ^5.7.0 hardhat-deploy: ^0.11.14 module-alias: ^2.2.2 - checksum: 6d5ead8b99a2e0cd0b0afd5b610eb83819c948ddd43de75361e1b4489ddfb1647cd63877a9dff6473b957250d85f38e4779876e871e73e8d74a6fd2c0b833b27 + checksum: 6ada3414a988a5308590fedaf1ad13bd7f58fed3db68170ab0f07910607fe3aa6a1e954939fb68c022e847c2729e94253b1da36c32b6753d1752cc3f7c036560 languageName: node linkType: hard -"@venusprotocol/protocol-reserve@npm:1.2.0-dev.2": - version: 1.2.0-dev.2 - resolution: "@venusprotocol/protocol-reserve@npm:1.2.0-dev.2" +"@venusprotocol/protocol-reserve@npm:^1.4.0": + version: 1.4.0 + resolution: "@venusprotocol/protocol-reserve@npm:1.4.0" dependencies: "@nomiclabs/hardhat-ethers": ^2.2.3 "@openzeppelin/contracts": ^4.8.3 "@openzeppelin/contracts-upgradeable": ^4.8.3 "@openzeppelin/hardhat-upgrades": ^1.21.0 "@solidity-parser/parser": ^0.13.2 - "@venusprotocol/solidity-utilities": ^1.0.1 + "@venusprotocol/isolated-pools": ^2.3.0 + "@venusprotocol/solidity-utilities": ^1.3.0 ethers: ^5.7.0 hardhat-deploy: ^0.11.14 module-alias: ^2.2.2 - checksum: e644bc58b6b4646ce6b262e9e34646d65285dee97cb38a34ac99d35330b3f4bea4c9075d87ee6e6d1e813563c622a01e12eba82014e6d7952e17ea798673bac3 + checksum: 6b9bc35ac7cdb2d828312c9ebcba1c55e3b63d6d8d237affa7b5baf94b5c2a86e296552c153a5c077f9b6178cc73c7fd20804126509a8a8670c5b50eeae41d2d languageName: node linkType: hard @@ -3214,6 +3331,28 @@ __metadata: languageName: node linkType: hard +"@venusprotocol/solidity-utilities@npm:^1.2.0, @venusprotocol/solidity-utilities@npm:^1.3.0": + version: 1.3.0 + resolution: "@venusprotocol/solidity-utilities@npm:1.3.0" + checksum: d1109365a5e01959c47b25fb129373db93792e60bf1bc0ed324b63c2a64f6e4a7878ebf016cfade94bc41a2c1245d3e861fdc6b8c5844ac210ed1d73e7307e72 + languageName: node + linkType: hard + +"@venusprotocol/token-bridge@npm:1.0.0": + version: 1.0.0 + resolution: "@venusprotocol/token-bridge@npm:1.0.0" + dependencies: + "@layerzerolabs/solidity-examples": ^1.0.0 + "@openzeppelin/contracts": ^4.8.3 + "@openzeppelin/contracts-upgradeable": ^4.8.3 + "@openzeppelin/hardhat-upgrades": ^1.21.0 + "@solidity-parser/parser": ^0.13.2 + ethers: ^5.7.0 + module-alias: ^2.2.2 + checksum: fa494157cc2493bcb4b2824fd545f2093b459404a449da6710c3c62ebd2638abc42cd15a06f60a7d537eb30e7e2efeab90961e34a60ecfef5d9c33c8e26b9405 + languageName: node + linkType: hard + "@venusprotocol/venus-protocol@npm:0.7.0": version: 0.7.0 resolution: "@venusprotocol/venus-protocol@npm:0.7.0" @@ -3226,19 +3365,21 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/venus-protocol@npm:6.1.0-dev.8": - version: 6.1.0-dev.8 - resolution: "@venusprotocol/venus-protocol@npm:6.1.0-dev.8" +"@venusprotocol/venus-protocol@npm:^7.1.0": + version: 7.1.0 + resolution: "@venusprotocol/venus-protocol@npm:7.1.0" dependencies: "@openzeppelin/contracts": 4.9.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@venusprotocol/governance-contracts": ^1.4.0-dev.2 - "@venusprotocol/protocol-reserve": 1.2.0-dev.2 - "@venusprotocol/solidity-utilities": ^1.1.0 + "@venusprotocol/governance-contracts": ^1.4.0 + "@venusprotocol/isolated-pools": 2.3.0 + "@venusprotocol/protocol-reserve": ^1.2.0 + "@venusprotocol/solidity-utilities": ^1.2.0 + "@venusprotocol/token-bridge": 1.0.0 bignumber.js: ^9.1.2 dotenv: ^16.0.1 module-alias: ^2.2.2 - checksum: 61e9d61128ea36aed3781d7373ec874233e9c2b2267ca1774f39139fb6a7c0844836be447084b36404fead74dc1efcae71330c081a4cca7563a061a73cb803c2 + checksum: 920713bb19dd66ab74652036b9214b9c3671313297fe577fa3f136da53898e4276c85d422b4d6418b0e8d67e5afac589de02771aef13fca73c2536b06a3ac7ec languageName: node linkType: hard @@ -3345,6 +3486,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:0.10.0": + version: 0.10.0 + resolution: "abitype@npm:0.10.0" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 01a75393740036121414024aa7ed61e6a2104bfd90c91b6aa1a7778cf1edfa15b828779acbbb13ac641939d1ba9c836d143d9f7310699cd7496273bb24c599b3 + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -4521,7 +4677,7 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:^0.6.3": +"cli-table3@npm:^0.6.0, cli-table3@npm:^0.6.3": version: 0.6.3 resolution: "cli-table3@npm:0.6.3" dependencies: @@ -5185,6 +5341,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: f412c5fe8c24fbe313d302d2500e247ba8a1946492db405a4de4d30dd0eb186a88a43f13c958c5a7de303938949c4231c56994f97d05c4bc1f22478d631b4005 + languageName: node + linkType: hard + "dotenv@npm:^16.0.1, dotenv@npm:^16.3.1": version: 16.3.1 resolution: "dotenv@npm:16.3.1" @@ -5294,6 +5457,13 @@ __metadata: languageName: node linkType: hard +"erc721a@npm:^4.2.3": + version: 4.2.3 + resolution: "erc721a@npm:4.2.3" + checksum: 0b2223678219d9b4a228da880cca9aab396ebe9cb59baf34a5501d4d9d297024d492ee5335c1983cd4c3bd15deef5e162c4837d27ac5ab5bf66e01591ead425c + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -6783,6 +6953,19 @@ __metadata: languageName: node linkType: hard +"hardhat-contract-sizer@npm:^2.1.1": + version: 2.10.0 + resolution: "hardhat-contract-sizer@npm:2.10.0" + dependencies: + chalk: ^4.0.0 + cli-table3: ^0.6.0 + strip-ansi: ^6.0.0 + peerDependencies: + hardhat: ^2.0.0 + checksum: 870e7cad5d96ad7288b64da0faec7962a9a18e1eaaa02ed474e4f9285cd4b1a0fc6f66326e6a7476f7063fdf99aee57f227084519b1fb3723700a2d65fc65cfa + languageName: node + linkType: hard + "hardhat-deploy-ethers@npm:^0.3.0-beta.13": version: 0.3.0-beta.13 resolution: "hardhat-deploy-ethers@npm:0.3.0-beta.13" @@ -6793,6 +6976,39 @@ __metadata: languageName: node linkType: hard +"hardhat-deploy@npm:^0.10.5": + version: 0.10.6 + resolution: "hardhat-deploy@npm:0.10.6" + dependencies: + "@ethersproject/abi": ^5.4.0 + "@ethersproject/abstract-signer": ^5.4.1 + "@ethersproject/address": ^5.4.0 + "@ethersproject/bignumber": ^5.4.1 + "@ethersproject/bytes": ^5.4.0 + "@ethersproject/constants": ^5.4.0 + "@ethersproject/contracts": ^5.4.1 + "@ethersproject/providers": ^5.4.4 + "@ethersproject/solidity": ^5.4.0 + "@ethersproject/transactions": ^5.4.0 + "@ethersproject/wallet": ^5.4.0 + "@types/qs": ^6.9.7 + axios: ^0.21.1 + chalk: ^4.1.2 + chokidar: ^3.5.2 + debug: ^4.3.2 + enquirer: ^2.3.6 + form-data: ^4.0.0 + fs-extra: ^10.0.0 + match-all: ^1.2.6 + murmur-128: ^0.2.1 + qs: ^6.9.4 + peerDependencies: + "@ethersproject/hardware-wallets": ^5.0.14 + hardhat: ^2.6.8 + checksum: dd82fafd74da7f7d180c529ea7a044b8c9c9c0d65d5e05285bafeeb3f864492ffb2271174bc2ac4ed963d60f4bc3e95e4b70e4e063ee23807f34fbf6a29a8681 + languageName: node + linkType: hard + "hardhat-deploy@npm:^0.11.14, hardhat-deploy@npm:^0.11.44": version: 0.11.44 resolution: "hardhat-deploy@npm:0.11.44" @@ -6825,7 +7041,7 @@ __metadata: languageName: node linkType: hard -"hardhat-gas-reporter@npm:^1.0.9": +"hardhat-gas-reporter@npm:^1.0.6, hardhat-gas-reporter@npm:^1.0.9": version: 1.0.9 resolution: "hardhat-gas-reporter@npm:1.0.9" dependencies: @@ -6904,6 +7120,72 @@ __metadata: languageName: node linkType: hard +"hardhat@npm:^2.8.0": + version: 2.19.4 + resolution: "hardhat@npm:2.19.4" + dependencies: + "@ethersproject/abi": ^5.1.2 + "@metamask/eth-sig-util": ^4.0.0 + "@nomicfoundation/ethereumjs-block": 5.0.2 + "@nomicfoundation/ethereumjs-blockchain": 7.0.2 + "@nomicfoundation/ethereumjs-common": 4.0.2 + "@nomicfoundation/ethereumjs-evm": 2.0.2 + "@nomicfoundation/ethereumjs-rlp": 5.0.2 + "@nomicfoundation/ethereumjs-statemanager": 2.0.2 + "@nomicfoundation/ethereumjs-trie": 6.0.2 + "@nomicfoundation/ethereumjs-tx": 5.0.2 + "@nomicfoundation/ethereumjs-util": 9.0.2 + "@nomicfoundation/ethereumjs-vm": 7.0.2 + "@nomicfoundation/solidity-analyzer": ^0.1.0 + "@sentry/node": ^5.18.1 + "@types/bn.js": ^5.1.0 + "@types/lru-cache": ^5.1.0 + adm-zip: ^0.4.16 + aggregate-error: ^3.0.0 + ansi-escapes: ^4.3.0 + chalk: ^2.4.2 + chokidar: ^3.4.0 + ci-info: ^2.0.0 + debug: ^4.1.1 + enquirer: ^2.3.0 + env-paths: ^2.2.0 + ethereum-cryptography: ^1.0.3 + ethereumjs-abi: ^0.6.8 + find-up: ^2.1.0 + fp-ts: 1.19.3 + fs-extra: ^7.0.1 + glob: 7.2.0 + immutable: ^4.0.0-rc.12 + io-ts: 1.10.4 + keccak: ^3.0.2 + lodash: ^4.17.11 + mnemonist: ^0.38.0 + mocha: ^10.0.0 + p-map: ^4.0.0 + raw-body: ^2.4.1 + resolve: 1.17.0 + semver: ^6.3.0 + solc: 0.7.3 + source-map-support: ^0.5.13 + stacktrace-parser: ^0.1.10 + tsort: 0.0.1 + undici: ^5.14.0 + uuid: ^8.3.2 + ws: ^7.4.6 + peerDependencies: + ts-node: "*" + typescript: "*" + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + bin: + hardhat: internal/cli/bootstrap.js + checksum: 05dcaeab5bb641e74426ad47acfda903dcd3fd229b0d30f45b9de1d3c54fe6364161f3c88517984233d18f5b9294a050500ca7336b6ca069fa259fede6f5acb1 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -7692,6 +7974,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.3": + version: 1.0.3 + resolution: "isows@npm:1.0.3" + peerDependencies: + ws: "*" + checksum: 9cacd5cf59f67deb51e825580cd445ab1725ecb05a67c704050383fb772856f3cd5e7da8ad08f5a3bd2823680d77d099459d0c6a7037972a74d6429af61af440 + languageName: node + linkType: hard + "issue-parser@npm:^6.0.0": version: 6.0.0 resolution: "issue-parser@npm:6.0.0" @@ -12178,6 +12469,27 @@ __metadata: languageName: node linkType: hard +"viem@npm:^2.1.1": + version: 2.1.1 + resolution: "viem@npm:2.1.1" + dependencies: + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@scure/bip32": 1.3.2 + "@scure/bip39": 1.2.1 + abitype: 0.10.0 + isows: 1.0.3 + ws: 8.13.0 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 3ff1c402188fd937341476fe46d5849aec5254070236d88625fcfe92d3ef2947e8ab54e3ffed2ba794c2371b6370410fe7663e49ff93d480c575129394ac5ffc + languageName: node + linkType: hard + "walk-up-path@npm:^3.0.1": version: 3.0.1 resolution: "walk-up-path@npm:3.0.1" @@ -12380,6 +12692,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.13.0": + version: 8.13.0 + resolution: "ws@npm:8.13.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c + languageName: node + linkType: hard + "ws@npm:^7.4.6": version: 7.5.9 resolution: "ws@npm:7.5.9"