diff --git a/contracts/polygon/common/interfaces.sol b/contracts/polygon/common/interfaces.sol index 5041400a..a38be52a 100644 --- a/contracts/polygon/common/interfaces.sol +++ b/contracts/polygon/common/interfaces.sol @@ -10,6 +10,7 @@ interface TokenInterface { function withdraw(uint) external; function balanceOf(address) external view returns (uint); function decimals() external view returns (uint); + function allowance(address owner, address spender) external view returns (uint256); } interface MemoryInterface { diff --git a/contracts/polygon/connectors/compound/v3-rewards/events.sol b/contracts/polygon/connectors/compound/v3-rewards/events.sol new file mode 100644 index 00000000..a279d988 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3-rewards/events.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogRewardsClaimed( + address indexed market, + address indexed account, + uint256 indexed rewardsClaimed, + uint256 setId + ); + + event LogRewardsClaimedOnBehalf( + address indexed market, + address indexed owner, + address to, + uint256 indexed rewardsClaimed, + uint256 setId + ); +} diff --git a/contracts/polygon/connectors/compound/v3-rewards/helpers.sol b/contracts/polygon/connectors/compound/v3-rewards/helpers.sol new file mode 100644 index 00000000..b4f7c125 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3-rewards/helpers.sol @@ -0,0 +1,12 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import { CometRewards } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + CometRewards internal constant cometRewards = + CometRewards(0x45939657d1CA34A8FA39A924B71D28Fe8431e581); +} diff --git a/contracts/polygon/connectors/compound/v3-rewards/interface.sol b/contracts/polygon/connectors/compound/v3-rewards/interface.sol new file mode 100644 index 00000000..26e633b3 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3-rewards/interface.sol @@ -0,0 +1,37 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +struct UserCollateral { + uint128 balance; + uint128 _reserved; +} + +struct RewardOwed { + address token; + uint256 owed; +} + +interface CometRewards { + function claim( + address comet, + address src, + bool shouldAccrue + ) external; + + function claimTo( + address comet, + address src, + address to, + bool shouldAccrue + ) external; + + function getRewardOwed(address comet, address account) + external + returns (RewardOwed memory); + + function rewardsClaimed(address cometProxy, address account) + external + view + returns (uint256); +} diff --git a/contracts/polygon/connectors/compound/v3-rewards/main.sol b/contracts/polygon/connectors/compound/v3-rewards/main.sol new file mode 100644 index 00000000..ce86a7af --- /dev/null +++ b/contracts/polygon/connectors/compound/v3-rewards/main.sol @@ -0,0 +1,68 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title Compound Polygon. + * @dev Rewards. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Stores } from "../../../common/stores.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract CompoundV3RewardsResolver is Events, Helpers { + /** + * @dev Claim rewards and interests accrued in supplied/borrowed base asset. + * @notice Claim rewards and interests accrued. + * @param market The address of the market. + * @param setId ID stores the amount of rewards claimed. + */ + function claimRewards( + address market, + uint256 setId + ) public returns (string memory eventName_, bytes memory eventParam_) { + uint256 rewardsOwed = cometRewards.getRewardOwed(market, address(this)).owed; + cometRewards.claim(market, address(this), true); + + setUint(setId, rewardsOwed); + + eventName_ = "LogRewardsClaimed(address,address,uint256,uint256)"; + eventParam_ = abi.encode(market, address(this), rewardsOwed, setId); + } + + /** + * @dev Claim rewards and interests accrued in supplied/borrowed base asset. + * @notice Claim rewards and interests accrued and transfer to dest address. + * @param market The address of the market. + * @param owner The account of which the rewards are to be claimed. + * @param to The account where to transfer the claimed rewards. + * @param setId ID stores the amount of rewards claimed. + */ + function claimRewardsOnBehalfOf( + address market, + address owner, + address to, + uint256 setId + ) public returns (string memory eventName_, bytes memory eventParam_) { + //in reward token decimals + uint256 rewardsOwed = cometRewards.getRewardOwed(market, owner).owed; + cometRewards.claimTo(market, owner, to, true); + + setUint(setId, rewardsOwed); + + eventName_ = "LogRewardsClaimedOnBehalf(address,address,address,uint256,uint256)"; + eventParam_ = abi.encode( + market, + owner, + to, + rewardsOwed, + setId + ); + } +} + +contract ConnectV2CompoundV3PolygonRewards is CompoundV3RewardsResolver { + string public constant name = "CompoundV3-Polygon-Rewards-v1.0"; +} diff --git a/contracts/polygon/connectors/compound/v3/events.sol b/contracts/polygon/connectors/compound/v3/events.sol new file mode 100644 index 00000000..6de419f6 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3/events.sol @@ -0,0 +1,165 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + address indexed market, + address indexed token, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogDepositOnBehalf( + address indexed market, + address indexed token, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogDepositFromUsingManager( + address indexed market, + address indexed token, + address from, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdraw( + address indexed market, + address indexed token, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdrawTo( + address indexed market, + address indexed token, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdrawOnBehalf( + address indexed market, + address indexed token, + address from, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdrawOnBehalfAndTransfer( + address indexed market, + address indexed token, + address from, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrow( + address indexed market, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrowTo( + address indexed market, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrowOnBehalf( + address indexed market, + address from, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrowOnBehalfAndTransfer( + address indexed market, + address from, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogPayback(address indexed market, uint256 tokenAmt, uint256 getId, uint256 setId); + + event LogPaybackOnBehalf( + address indexed market, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogPaybackFromUsingManager( + address indexed market, + address from, + address to, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBuyCollateral( + address indexed market, + address indexed buyToken, + uint256 indexed baseSellAmt, + uint256 unitAmt, + uint256 buyAmount, + uint256 getId, + uint256 setId + ); + + event LogTransferAsset( + address indexed market, + address token, + address indexed dest, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogTransferAssetOnBehalf( + address indexed market, + address token, + address indexed from, + address indexed dest, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogToggleAccountManager( + address indexed market, + address indexed manager, + bool allow + ); + + event LogToggleAccountManagerWithPermit( + address indexed market, + address indexed owner, + address indexed manager, + bool allow, + uint256 expiry, + uint256 nonce, + uint8 v, + bytes32 r, + bytes32 s + ); +} diff --git a/contracts/polygon/connectors/compound/v3/helpers.sol b/contracts/polygon/connectors/compound/v3/helpers.sol new file mode 100644 index 00000000..1fd3c0c1 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3/helpers.sol @@ -0,0 +1,273 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import { CometInterface } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + struct BorrowWithdrawParams { + address market; + address token; + address from; + address to; + uint256 amt; + uint256 getId; + uint256 setId; + } + + struct BuyCollateralData { + address market; + address sellToken; + address buyAsset; + uint256 unitAmt; + uint256 baseSellAmt; + } + + enum Action { + REPAY, + DEPOSIT + } + + function getBaseToken(address market) + internal + view + returns (address baseToken) + { + baseToken = CometInterface(market).baseToken(); + } + + function _borrow(BorrowWithdrawParams memory params) + internal + returns (uint256 amt, uint256 setId) + { + uint256 amt_ = getUint(params.getId, params.amt); + + require( + params.market != address(0) && + params.token != address(0) && + params.to != address(0), + "invalid market/token/to address" + ); + bool isMatic = params.token == maticAddr; + address token_ = isMatic ? wmaticAddr : params.token; + + TokenInterface tokenContract = TokenInterface(token_); + + params.from = params.from == address(0) ? address(this) : params.from; + + require( + CometInterface(params.market).balanceOf(params.from) == 0, + "borrow-disabled-when-supplied-base" + ); + + uint256 initialBal = CometInterface(params.market).borrowBalanceOf( + params.from + ); + + CometInterface(params.market).withdrawFrom( + params.from, + params.to, + token_, + amt_ + ); + + uint256 finalBal = CometInterface(params.market).borrowBalanceOf( + params.from + ); + amt_ = sub(finalBal, initialBal); + + if (params.to == address(this)) + convertWmaticToMatic(isMatic, tokenContract, amt_); + + setUint(params.setId, amt_); + + amt = amt_; + setId = params.setId; + } + + function _withdraw(BorrowWithdrawParams memory params) + internal + returns (uint256 amt, uint256 setId) + { + uint256 amt_ = getUint(params.getId, params.amt); + + require( + params.market != address(0) && + params.token != address(0) && + params.to != address(0), + "invalid market/token/to address" + ); + + bool isMatic = params.token == maticAddr; + address token_ = isMatic ? wmaticAddr : params.token; + + TokenInterface tokenContract = TokenInterface(token_); + params.from = params.from == address(0) ? address(this) : params.from; + + uint256 initialBal = _getAccountSupplyBalanceOfAsset( + params.from, + params.market, + token_ + ); + + if (token_ == getBaseToken(params.market)) { + //if there are supplies, ensure withdrawn amount is not greater than supplied i.e can't borrow using withdraw. + if (amt_ == uint256(-1)) { + amt_ = initialBal; + } else { + require( + amt_ <= initialBal, + "withdraw-amt-greater-than-supplies" + ); + } + + //if borrow balance > 0, there are no supplies so no withdraw, borrow instead. + require( + CometInterface(params.market).borrowBalanceOf(params.from) == 0, + "withdraw-disabled-for-zero-supplies" + ); + } else { + amt_ = amt_ == uint256(-1) ? initialBal : amt_; + } + + CometInterface(params.market).withdrawFrom( + params.from, + params.to, + token_, + amt_ + ); + + uint256 finalBal = _getAccountSupplyBalanceOfAsset( + params.from, + params.market, + token_ + ); + amt_ = sub(initialBal, finalBal); + + if (params.to == address(this)) + convertWmaticToMatic(isMatic, tokenContract, amt_); + + setUint(params.setId, amt_); + + amt = amt_; + setId = params.setId; + } + + function _getAccountSupplyBalanceOfAsset( + address account, + address market, + address asset + ) internal returns (uint256 balance) { + if (asset == getBaseToken(market)) { + //balance in base + balance = CometInterface(market).balanceOf(account); + } else { + //balance in asset denomination + balance = uint256( + CometInterface(market).userCollateral(account, asset).balance + ); + } + } + + function _calculateFromAmount( + address market, + address token, + address src, + uint256 amt, + bool isMatic, + Action action + ) internal view returns (uint256) { + if (amt == uint256(-1)) { + uint256 allowance_ = TokenInterface(token).allowance(src, market); + uint256 bal_; + + if (action == Action.REPAY) { + bal_ = CometInterface(market).borrowBalanceOf(src); + } else if (action == Action.DEPOSIT) { + if (isMatic) bal_ = src.balance; + else bal_ = TokenInterface(token).balanceOf(src); + } + + amt = bal_ < allowance_ ? bal_ : allowance_; + } + + return amt; + } + + function _buyCollateral( + BuyCollateralData memory params, + uint256 getId, + uint256 setId + ) internal returns (string memory eventName_, bytes memory eventParam_) { + uint256 sellAmt_ = getUint(getId, params.baseSellAmt); + require( + params.market != address(0) && params.buyAsset != address(0), + "invalid market/token address" + ); + + bool isMatic = params.sellToken == maticAddr; + params.sellToken = isMatic ? wmaticAddr : params.sellToken; + + require( + params.sellToken == getBaseToken(params.market), + "invalid-sell-token" + ); + + if (sellAmt_ == uint256(-1)) { + sellAmt_ = isMatic + ? address(this).balance + : TokenInterface(params.sellToken).balanceOf(address(this)); + } + convertMaticToWmatic(isMatic, TokenInterface(params.sellToken), sellAmt_); + + isMatic = params.buyAsset == maticAddr; + params.buyAsset = isMatic ? wmaticAddr : params.buyAsset; + + uint256 slippageAmt_ = convert18ToDec( + TokenInterface(params.buyAsset).decimals(), + wmul( + params.unitAmt, + convertTo18( + TokenInterface(params.sellToken).decimals(), + sellAmt_ + ) + ) + ); + + uint256 initialCollBal_ = TokenInterface(params.buyAsset).balanceOf( + address(this) + ); + + approve(TokenInterface(params.sellToken), params.market, sellAmt_); + CometInterface(params.market).buyCollateral( + params.buyAsset, + slippageAmt_, + sellAmt_, + address(this) + ); + + uint256 finalCollBal_ = TokenInterface(params.buyAsset).balanceOf( + address(this) + ); + + uint256 buyAmt_ = sub(finalCollBal_, initialCollBal_); + require(slippageAmt_ <= buyAmt_, "too-much-slippage"); + + convertWmaticToMatic(isMatic, TokenInterface(params.buyAsset), buyAmt_); + setUint(setId, sellAmt_); + + eventName_ = "LogBuyCollateral(address,address,uint256,uint256,uint256,uint256,uint256)"; + eventParam_ = abi.encode( + params.market, + params.buyAsset, + sellAmt_, + params.unitAmt, + buyAmt_, + getId, + setId + ); + } +} diff --git a/contracts/polygon/connectors/compound/v3/interface.sol b/contracts/polygon/connectors/compound/v3/interface.sol new file mode 100644 index 00000000..3b1920d1 --- /dev/null +++ b/contracts/polygon/connectors/compound/v3/interface.sol @@ -0,0 +1,121 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +struct UserCollateral { + uint128 balance; + uint128 _reserved; +} + +struct RewardOwed { + address token; + uint256 owed; +} + +interface CometInterface { + function supply(address asset, uint256 amount) external virtual; + + function supplyTo( + address dst, + address asset, + uint256 amount + ) external virtual; + + function supplyFrom( + address from, + address dst, + address asset, + uint256 amount + ) external virtual; + + function transfer(address dst, uint256 amount) + external + virtual + returns (bool); + + function transferFrom( + address src, + address dst, + uint256 amount + ) external virtual returns (bool); + + function transferAsset( + address dst, + address asset, + uint256 amount + ) external virtual; + + function transferAssetFrom( + address src, + address dst, + address asset, + uint256 amount + ) external virtual; + + function withdraw(address asset, uint256 amount) external virtual; + + function withdrawTo( + address to, + address asset, + uint256 amount + ) external virtual; + + function withdrawFrom( + address src, + address to, + address asset, + uint256 amount + ) external virtual; + + function approveThis( + address manager, + address asset, + uint256 amount + ) external virtual; + + function withdrawReserves(address to, uint256 amount) external virtual; + + function absorb(address absorber, address[] calldata accounts) + external + virtual; + + function buyCollateral( + address asset, + uint256 minAmount, + uint256 baseAmount, + address recipient + ) external virtual; + + function quoteCollateral(address asset, uint256 baseAmount) + external + view + returns (uint256); + + function userCollateral(address, address) + external + returns (UserCollateral memory); + + function baseToken() external view returns (address); + + function balanceOf(address account) external view returns (uint256); + + function borrowBalanceOf(address account) external view returns (uint256); + + function allow(address manager, bool isAllowed_) external; + + function allowance(address owner, address spender) + external + view + returns (uint256); + + function allowBySig( + address owner, + address manager, + bool isAllowed_, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/contracts/polygon/connectors/compound/v3/main.sol b/contracts/polygon/connectors/compound/v3/main.sol new file mode 100644 index 00000000..b0b98dbe --- /dev/null +++ b/contracts/polygon/connectors/compound/v3/main.sol @@ -0,0 +1,932 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title Compound III Polygon. + * @dev Lending & Borrowing. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import { CometInterface } from "./interface.sol"; + +abstract contract CompoundV3Contract is Events, Helpers { + /** + * @dev Deposit base asset or collateral asset supported by the market. + * @notice Deposit a token to Compound for lending / collaterization. + * @param market The address of the market. + * @param token The address of the token to be supplied. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function deposit( + address market, + address token, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + + require( + market != address(0) && token != address(0), + "invalid market/token address" + ); + + bool isMatic = token == maticAddr; + address token_ = isMatic ? wmaticAddr : token; + TokenInterface tokenContract = TokenInterface(token_); + + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(address(this)) == 0, + "debt-not-repaid" + ); + } + + if (isMatic) { + amt_ = amt_ == uint256(-1) ? address(this).balance : amt_; + convertMaticToWmatic(isMatic, tokenContract, amt_); + } else { + amt_ = amt_ == uint256(-1) + ? tokenContract.balanceOf(address(this)) + : amt_; + } + approve(tokenContract, market, amt_); + + CometInterface(market).supply(token_, amt_); + setUint(setId, amt_); + + eventName_ = "LogDeposit(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, amt_, getId, setId); + } + + /** + * @dev Deposit base asset or collateral asset supported by the market on behalf of 'to'. + * @notice Deposit a token to Compound for lending / collaterization on behalf of 'to'. + * @param market The address of the market. + * @param token The address of the token to be supplied. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE). + * @param to The address on behalf of which the supply is made. + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function depositOnBehalf( + address market, + address token, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + + require( + market != address(0) && token != address(0) && to != address(0), + "invalid market/token/to address" + ); + + bool isMatic = token == maticAddr; + address token_ = isMatic ? wmaticAddr : token; + TokenInterface tokenContract = TokenInterface(token_); + + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(to) == 0, + "to-address-position-debt-not-repaid" + ); + } + + if (isMatic) { + amt_ = amt_ == uint256(-1) ? address(this).balance : amt_; + convertMaticToWmatic(isMatic, tokenContract, amt_); + } else { + amt_ = amt_ == uint256(-1) + ? tokenContract.balanceOf(address(this)) + : amt_; + } + approve(tokenContract, market, amt_); + + CometInterface(market).supplyTo(to, token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogDepositOnBehalf(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, to, amt_, getId, setId); + } + + /** + * @dev Deposit base asset or collateral asset supported by the market from 'from' address and update the position of 'to'. + * @notice Deposit a token to Compound for lending / collaterization from a address and update the position of 'to'. + * @param market The address of the market. + * @param token The address of the token to be supplied. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param from The address from where amount is to be supplied. + * @param to The address on account of which the supply is made or whose positions are updated. + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function depositFromUsingManager( + address market, + address token, + address from, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + + require( + market != address(0) && token != address(0) && to != address(0), + "invalid market/token/to address" + ); + require(from != address(this), "from-cannot-be-address(this)-use-depositOnBehalf"); + + bool isMatic = token == maticAddr; + address token_ = isMatic? wmaticAddr : token; + + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(to) == 0, + "to-address-position-debt-not-repaid" + ); + } + + amt_ = _calculateFromAmount( + market, + token_, + from, + amt_, + isMatic, + Action.DEPOSIT + ); + + CometInterface(market).supplyFrom(from, to, token_, amt_); + setUint(setId, amt_); + + eventName_ = "LogDepositFromUsingManager(address,address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, from, to, amt_, getId, setId); + } + + /** + * @dev Withdraw base/collateral asset. + * @notice Withdraw base token or deposited token from Compound. + * @param market The address of the market. + * @param token The address of the token to be withdrawn. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdraw( + address market, + address token, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + + require( + market != address(0) && token != address(0), + "invalid market/token address" + ); + + bool isMatic = token == maticAddr; + address token_ = isMatic ? wmaticAddr : token; + + TokenInterface tokenContract = TokenInterface(token_); + + uint256 initialBal = _getAccountSupplyBalanceOfAsset( + address(this), + market, + token_ + ); + + if (token_ == getBaseToken(market)) { + if (amt_ == uint256(-1)) { + amt_ = initialBal; + } else { + //if there are supplies, ensure withdrawn amount is not greater than supplied i.e can't borrow using withdraw. + require(amt_ <= initialBal, "withdraw-amt-greater-than-supplies"); + } + + //if borrow balance > 0, there are no supplies so no withdraw, borrow instead. + require( + CometInterface(market).borrowBalanceOf(address(this)) == 0, + "withdraw-disabled-for-zero-supplies" + ); + } else { + amt_ = amt_ == uint256(-1) ? initialBal : amt_; + } + + CometInterface(market).withdraw(token_, amt_); + + uint256 finalBal = _getAccountSupplyBalanceOfAsset( + address(this), + market, + token_ + ); + + amt_ = sub(initialBal, finalBal); + + convertWmaticToMatic(isMatic, tokenContract, amt_); + + setUint(setId, amt_); + + eventName_ = "LogWithdraw(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, amt_, getId, setId); + } + + /** + * @dev Withdraw base/collateral asset and transfer to 'to'. + * @notice Withdraw base token or deposited token from Compound on behalf of an address and transfer to 'to'. + * @param market The address of the market. + * @param token The address of the token to be withdrawn. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param to The address to which the borrowed assets are to be transferred. + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawTo( + address market, + address token, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + (uint256 amt_, uint256 setId_) = _withdraw( + BorrowWithdrawParams({ + market: market, + token: token, + from: address(this), + to: to, + amt: amt, + getId: getId, + setId: setId + }) + ); + + eventName_ = "LogWithdrawTo(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, to, amt_, getId, setId_); + } + + /** + * @dev Withdraw base/collateral asset from an account and transfer to DSA. + * @notice Withdraw base token or deposited token from Compound from an address and transfer to DSA. + * @param market The address of the market. + * @param token The address of the token to be withdrawn. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param from The address from where asset is to be withdrawed. + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawOnBehalf( + address market, + address token, + address from, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + (uint256 amt_, uint256 setId_) = _withdraw( + BorrowWithdrawParams({ + market: market, + token: token, + from: from, + to: address(this), + amt: amt, + getId: getId, + setId: setId + }) + ); + + eventName_ = "LogWithdrawOnBehalf(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, from, amt_, getId, setId_); + } + + /** + * @dev Withdraw base/collateral asset from an account and transfer to 'to'. + * @notice Withdraw base token or deposited token from Compound from an address and transfer to 'to'. + * @param market The address of the market. + * @param token The address of the token to be withdrawn. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param from The address from where asset is to be withdrawed. + * @param to The address to which the borrowed assets are to be transferred. + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawOnBehalfAndTransfer( + address market, + address token, + address from, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory eventName_, bytes memory eventParam_) + { + (uint256 amt_, uint256 setId_) = _withdraw( + BorrowWithdrawParams({ + market: market, + token: token, + from: from, + to: to, + amt: amt, + getId: getId, + setId: setId + }) + ); + + eventName_ = "LogWithdrawOnBehalfAndTransfer(address,address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, from, to, amt_, getId, setId_); + } + + /** + * @dev Borrow base asset. + * @notice Borrow base token from Compound. + * @param market The address of the market. + * @param token The address of the token to be borrowed. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of base token to borrow. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrow( + address market, + address token, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + + require(market != address(0), "invalid market address"); + + bool isMatic = token == maticAddr; + address token_ = getBaseToken(market); + require(token == token_ || isMatic, "invalid-token"); + + TokenInterface tokenContract = TokenInterface(token_); + + require( + CometInterface(market).balanceOf(address(this)) == 0, + "borrow-disabled-when-supplied-base" + ); + + uint256 initialBal = CometInterface(market).borrowBalanceOf( + address(this) + ); + CometInterface(market).withdraw(token_, amt_); + + uint256 finalBal = CometInterface(market).borrowBalanceOf( + address(this) + ); + + amt_ = sub(finalBal, initialBal); + + convertWmaticToMatic(isMatic, tokenContract, amt_); + + setUint(setId, amt_); + + eventName_ = "LogBorrow(address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, amt_, getId, setId); + } + + /** + * @dev Borrow base asset and transfer to 'to' account. + * @notice Borrow base token from Compound on behalf of an address. + * @param market The address of the market. + * @param token The address of the token to be borrowed. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param to The address to which the borrowed asset is transferred. + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrowTo( + address market, + address token, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + require( + token == maticAddr || token == getBaseToken(market), + "invalid-token" + ); + (uint256 amt_, uint256 setId_) = _borrow( + BorrowWithdrawParams({ + market: market, + token: token, + from: address(this), + to: to, + amt: amt, + getId: getId, + setId: setId + }) + ); + eventName_ = "LogBorrowTo(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, to, amt_, getId, setId_); + } + + /** + * @dev Borrow base asset from 'from' and transfer to 'to'. + * @notice Borrow base token or deposited token from Compound. + * @param market The address of the market. + * @param token The address of the token to be borrowed. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param from The address from where asset is to be withdrawed. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrowOnBehalf( + address market, + address token, + address from, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + require( + token == maticAddr || token == getBaseToken(market), + "invalid-token" + ); + (uint256 amt_, uint256 setId_) = _borrow( + BorrowWithdrawParams({ + market: market, + token: token, + from: from, + to: address(this), + amt: amt, + getId: getId, + setId: setId + }) + ); + eventName_ = "LogBorrowOnBehalf(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, from, amt_, getId, setId_); + } + + /** + * @dev Borrow base asset from 'from' and transfer to 'to'. + * @notice Borrow base token or deposited token from Compound. + * @param market The address of the market. + * @param token The address of the token to be borrowed. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param from The address from where asset is to be withdrawed. + * @param to The address to which the borrowed assets are to be transferred. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrowOnBehalfAndTransfer( + address market, + address token, + address from, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + require( + token == maticAddr || token == getBaseToken(market), + "invalid-token" + ); + (uint256 amt_, uint256 setId_) = _borrow( + BorrowWithdrawParams({ + market: market, + token: token, + from: from, + to: to, + amt: amt, + getId: getId, + setId: setId + }) + ); + eventName_ = "LogBorrowOnBehalfAndTransfer(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, from, to, amt_, getId, setId_); + } + + /** + * @dev Repays the borrowed base asset. + * @notice Repays the borrow of the base asset. + * @param market The address of the market. + * @param token The address of the token to be repaid. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount to be repaid. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens repaid. + */ + function payback( + address market, + address token, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + require( + market != address(0) && token != address(0), + "invalid market/token address" + ); + + bool isMatic = token == maticAddr; + address token_ = getBaseToken(market); + require(token == token_ || isMatic, "invalid-token"); + + TokenInterface tokenContract = TokenInterface(token_); + + uint256 borrowedBalance_ = CometInterface(market).borrowBalanceOf( + address(this) + ); + + if (amt_ == uint256(-1)) { + amt_ = borrowedBalance_; + } else { + require( + amt_ <= borrowedBalance_, + "payback-amt-greater-than-borrows" + ); + } + + //if supply balance > 0, there are no borrowing so no repay, supply instead. + require( + CometInterface(market).balanceOf(address(this)) == 0, + "cannot-repay-when-supplied" + ); + + convertMaticToWmatic(isMatic, tokenContract, amt_); + approve(tokenContract, market, amt_); + + CometInterface(market).supply(token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogPayback(address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, amt_, getId, setId); + } + + /** + * @dev Repays borrow of the base asset on behalf of 'to'. + * @notice Repays borrow of the base asset on behalf of 'to'. + * @param market The address of the market. + * @param token The address of the token to be repaid. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param to The address on behalf of which the borrow is to be repaid. + * @param amt The amount to be repaid. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens repaid. + */ + function paybackOnBehalf( + address market, + address token, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + require( + market != address(0) && token != address(0) && to != address(0), + "invalid market/token/to address" + ); + + address token_ = getBaseToken(market); + bool isMatic = token == maticAddr; + require(token == token_ || isMatic, "invalid-token"); + + TokenInterface tokenContract = TokenInterface(token_); + + uint256 borrowedBalance_ = CometInterface(market).borrowBalanceOf(to); + + if (amt_ == uint256(-1)) { + amt_ = borrowedBalance_; + } else { + require( + amt_ <= borrowedBalance_, + "payback-amt-greater-than-borrows" + ); + } + + //if supply balance > 0, there are no borrowing so no repay, supply instead. + require( + CometInterface(market).balanceOf(to) == 0, + "cannot-repay-when-supplied" + ); + + convertMaticToWmatic(isMatic, tokenContract, amt_); + approve(tokenContract, market, amt_); + + CometInterface(market).supplyTo(to, token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogPaybackOnBehalf(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, to, amt_, getId, setId); + } + + /** + * @dev Repays borrow of the base asset form 'from' on behalf of 'to'. + * @notice Repays borrow of the base asset on behalf of 'to'. 'From' address must approve the comet market. + * @param market The address of the market. + * @param token The address of the token to be repaid. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param from The address from which the borrow has to be repaid on behalf of 'to'. + * @param to The address on behalf of which the borrow is to be repaid. + * @param amt The amount to be repaid. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens repaid. + */ + function paybackFromUsingManager( + address market, + address token, + address from, + address to, + uint256 amt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amt); + require( + market != address(0) && token != address(0) && to != address(0), + "invalid market/token/to address" + ); + require(from != address(this), "from-cannot-be-address(this)-use-paybackOnBehalf"); + + address token_ = getBaseToken(market); + bool isMatic = token == maticAddr; + require(token == token_ || isMatic, "invalid-token"); + + if (amt_ == uint256(-1)) { + amt_ = _calculateFromAmount( + market, + token_, + from, + amt_, + isMatic, + Action.REPAY + ); + } else { + uint256 borrowedBalance_ = CometInterface(market).borrowBalanceOf(to); + require( + amt_ <= borrowedBalance_, + "payback-amt-greater-than-borrows" + ); + } + + //if supply balance > 0, there are no borrowing so no repay, withdraw instead. + require( + CometInterface(market).balanceOf(to) == 0, + "cannot-repay-when-supplied" + ); + + CometInterface(market).supplyFrom(from, to, token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogPaybackFromUsingManager(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, from, to, amt_, getId, setId); + } + + /** + * @dev Buy collateral asset absorbed, from the market. + * @notice Buy collateral asset to increase protocol base reserves until targetReserves is reached. + * @param market The address of the market from where to withdraw. + * @param sellToken base token. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param buyAsset The collateral asset to purachase. (For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param unitAmt Minimum amount of collateral expected to be received. + * @param baseSellAmt Amount of base asset to be sold for collateral. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of base tokens sold. + */ + function buyCollateral( + address market, + address sellToken, + address buyAsset, + uint256 unitAmt, + uint256 baseSellAmt, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + (eventName_, eventParam_) = _buyCollateral( + BuyCollateralData({ + market: market, + sellToken: sellToken, + buyAsset: buyAsset, + unitAmt: unitAmt, + baseSellAmt: baseSellAmt + }), + getId, + setId + ); + } + + /** + * @dev Transfer base/collateral or base asset to dest address from this account. + * @notice Transfer base/collateral asset to dest address from caller's account. + * @param market The address of the market. + * @param token The collateral asset to transfer to dest address. + * @param dest The account where to transfer the base assets. + * @param amount The amount of the collateral token to transfer. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens transferred. + */ + function transferAsset( + address market, + address token, + address dest, + uint256 amount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amount); + require( + market != address(0) && token != address(0) && dest != address(0), + "invalid market/token/to address" + ); + + address token_ = token == maticAddr ? wmaticAddr : token; + + amt_ = amt_ == uint256(-1) ? _getAccountSupplyBalanceOfAsset(address(this), market, token) : amt_; + + CometInterface(market).transferAssetFrom(address(this), dest, token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogTransferAsset(address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token_, dest, amt_, getId, setId); + } + + /** + * @dev Transfer collateral or base asset to dest address from src account. + * @notice Transfer collateral asset to dest address from src's account. + * @param market The address of the market. + * @param token The collateral asset to transfer to dest address. + * @param src The account from where to transfer the collaterals. + * @param dest The account where to transfer the collateral assets. + * @param amount The amount of the collateral token to transfer. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens transferred. + */ + function transferAssetOnBehalf( + address market, + address token, + address src, + address dest, + uint256 amount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory eventName_, bytes memory eventParam_) + { + uint256 amt_ = getUint(getId, amount); + require( + market != address(0) && token != address(0) && dest != address(0), + "invalid market/token/to address" + ); + + address token_ = token == maticAddr ? wmaticAddr : token; + + amt_ = amt_ == uint256(-1) ? _getAccountSupplyBalanceOfAsset(src, market, token) : amt_; + + CometInterface(market).transferAssetFrom(src, dest, token_, amt_); + + setUint(setId, amt_); + + eventName_ = "LogTransferAssetOnBehalf(address,address,address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token_, src, dest, amt_, getId, setId); + } + + /** + * @dev Allow/Disallow managers to handle position. + * @notice Authorize/Remove managers to perform write operations for the position. + * @param market The address of the market where to supply. + * @param manager The address to be authorized. + * @param isAllowed Whether to allow or disallow the manager. + */ + function toggleAccountManager( + address market, + address manager, + bool isAllowed + ) external returns (string memory eventName_, bytes memory eventParam_) { + CometInterface(market).allow(manager, isAllowed); + eventName_ = "LogToggleAccountManager(address,address,bool)"; + eventParam_ = abi.encode(market, manager, isAllowed); + } + + /** + * @dev Allow/Disallow managers to handle owner's position. + * @notice Authorize/Remove managers to perform write operations for owner's position. + * @param market The address of the market where to supply. + * @param owner The authorizind owner account. + * @param manager The address to be authorized. + * @param isAllowed Whether to allow or disallow the manager. + * @param nonce Signer's nonce. + * @param expiry The duration for which to permit the manager. + * @param v Recovery byte of the signature. + * @param r Half of the ECDSA signature pair. + * @param s Half of the ECDSA signature pair. + */ + function toggleAccountManagerWithPermit( + address market, + address owner, + address manager, + bool isAllowed, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (string memory eventName_, bytes memory eventParam_) { + CometInterface(market).allowBySig( + owner, + manager, + isAllowed, + nonce, + expiry, + v, + r, + s + ); + eventName_ = "LogToggleAccountManagerWithPermit(address,address,address,bool,uint256,uint256,uint8,bytes32,bytes32)"; + eventParam_ = abi.encode( + market, + owner, + manager, + isAllowed, + expiry, + nonce, + v, + r, + s + ); + } +} + +contract ConnectV2CompoundV3Polygon is CompoundV3Contract { + string public constant name = "CompoundV3-Polygon-v1.0"; +} diff --git a/scripts/tests/polygon/tokens.ts b/scripts/tests/polygon/tokens.ts index 83b66b68..2b30e422 100644 --- a/scripts/tests/polygon/tokens.ts +++ b/scripts/tests/polygon/tokens.ts @@ -14,6 +14,13 @@ export const tokens = { address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", decimals: 18, }, + wmatic: { + type: "token", + symbol: "WMATIC", + name: "Wrapped Matic", + address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + decimals: 18, + }, eth: { type: "token", symbol: "ETH", diff --git a/test/polygon/compound/compound.iii.rewards.test.ts b/test/polygon/compound/compound.iii.rewards.test.ts new file mode 100644 index 00000000..08e688b0 --- /dev/null +++ b/test/polygon/compound/compound.iii.rewards.test.ts @@ -0,0 +1,448 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { Signer, Contract } from "ethers"; +import { BigNumber } from "bignumber.js"; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { tokens, tokenMapping } from "../../../scripts/tests/polygon/tokens"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2CompoundV3PolygonRewards__factory, ConnectV2CompoundV3Polygon__factory } from "../../../typechain"; + +describe("Compound III Rewards", function () { + let connectorName = "COMPOUND-V3-REWARDS-TEST-A"; + const market = "0xF25212E676D1F7F89Cd72fFEe66158f541246445"; + const rewards = "0x45939657d1CA34A8FA39A924B71D28Fe8431e581"; + const base = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"; + const account = "0xf827c3E5fD68e78aa092245D442398E12988901C"; + const wethWhale = "0xadbF1854e5883eB8aa7BAf50705338739e558E5b"; + const baseWhale = "0x72a53cdbbcc1b9efa39c834a540550e23463aacb"; + + const ABI = [ + "function balanceOf(address account) public view returns (uint256)", + "function approve(address spender, uint256 amount) external returns(bool)", + "function transfer(address recipient, uint256 amount) external returns (bool)" + ]; + const wethContract = new ethers.Contract(tokens.wmatic.address, ABI); + const baseContract = new ethers.Contract(base, ABI); + + const cometABI = [ + { + inputs: [ + { internalType: "address", name: "comet", type: "address" }, + { internalType: "address", name: "src", type: "address" }, + { internalType: "bool", name: "shouldAccrue", type: "bool" } + ], + name: "claim", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "comet", type: "address" }, + { internalType: "address", name: "src", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "bool", name: "shouldAccrue", type: "bool" } + ], + name: "claimTo", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "comet", type: "address" }, + { internalType: "address", name: "account", type: "address" } + ], + name: "getRewardOwed", + outputs: [ + { + components: [ + { internalType: "address", name: "token", type: "address" }, + { internalType: "uint256", name: "owed", type: "uint256" } + ], + internalType: "struct CometRewards.RewardOwed", + name: "", + type: "tuple" + } + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "rewardConfig", + outputs: [ + { internalType: "address", name: "token", type: "address" }, + { internalType: "uint64", name: "rescaleFactor", type: "uint64" }, + { internalType: "bool", name: "shouldUpscale", type: "bool" } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" } + ], + name: "rewardsClaimed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + } + ]; + + const marketABI = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "borrowBalanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseBorrowMin", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseMinForRewards", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseToken", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "priceFeed", type: "address" }], + name: "getPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "manager", type: "address" } + ], + name: "hasPermission", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "numAssets", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "baseAmount", type: "uint256" } + ], + name: "quoteCollateral", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "userBasic", + outputs: [ + { internalType: "int104", name: "principal", type: "int104" }, + { internalType: "uint64", name: "baseTrackingIndex", type: "uint64" }, + { internalType: "uint64", name: "baseTrackingAccrued", type: "uint64" }, + { internalType: "uint16", name: "assetsIn", type: "uint16" }, + { internalType: "uint8", name: "_reserved", type: "uint8" } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" } + ], + name: "userCollateral", + outputs: [ + { internalType: "uint128", name: "balance", type: "uint128" }, + { internalType: "uint128", name: "_reserved", type: "uint128" } + ], + stateMutability: "view", + type: "function" + } + ]; + + let dsaWallet0: any; + let dsaWallet1: any; + let wallet: any; + let dsa0Signer: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + let connectorMain: any; + let signer: any; + let wethSigner: any; + let usdcSigner: any; + + const cometReward = new ethers.Contract(rewards, cometABI); + const comet = new ethers.Contract(market, marketABI); + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + //@ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + // blockNumber: 15444500 + } + } + ] + }); + masterSigner = await getMasterSigner(); + await hre.network.provider.send("hardhat_setBalance", [ + await masterSigner.getAddress(), + "0x3635c9adc5de9fffff", + ]); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2CompoundV3PolygonRewards__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + + await hre.network.provider.send("hardhat_setBalance", [account, ethers.utils.parseEther("10").toHexString()]); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [account] + }); + + signer = await ethers.getSigner(account); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wethWhale] + }); + wethSigner = await ethers.getSigner(wethWhale); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [baseWhale] + }); + usdcSigner = await ethers.getSigner(baseWhale); + await hre.network.provider.send("hardhat_setBalance", [ + usdcSigner.address, + ethers.utils.parseEther("10").toHexString() + ]); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + dsaWallet1 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet1.address).to.be.true; + wallet = await ethers.getSigner(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wallet.address] + }); + + dsa0Signer = await ethers.getSigner(wallet.address); + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("should deposit USDC in dsa wallet", async function () { + await baseContract.connect(usdcSigner).transfer(dsaWallet0.address, ethers.utils.parseUnits("500", 6)); + + expect(await baseContract.connect(usdcSigner).balanceOf(dsaWallet0.address)).to.be.gte( + ethers.utils.parseUnits("500", 6) + ); + }); + }); + + describe("Main", function () { + //deposit asset + it("Should supply USDC in Compound V3", async function () { + connectorName = "COMPOUND-V3-TEST-A"; + connectorMain = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2CompoundV3Polygon__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + const amount = ethers.utils.parseUnits("400", 6); + const spells = [ + { + connector: "COMPOUND-V3-TEST-A", + method: "deposit", + args: [market, base, amount, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await baseContract.connect(signer).balanceOf(dsaWallet0.address)).toFixed(0)).to.be.lte( + ethers.utils.parseUnits("100", 6) + ); + expect(new BigNumber(await comet.connect(signer).balanceOf(dsaWallet0.address)).toFixed(0)).to.be.gte( + ethers.utils.parseUnits("399", 6) + ); + }); + + let connector_ = "COMPOUND-V3-REWARDS-TEST-A"; + it("Should claim rewards", async function () { + let reward = (await cometReward.connect(signer).rewardConfig(market)).token; + let rewardInterface = new ethers.Contract(reward, ABI); + let owed_ = await cometReward.connect(signer).callStatic.getRewardOwed(market, dsaWallet0.address); + let amt: number = owed_.owed; + console.log(new BigNumber(amt).toFixed(0)); + const spells = [ + { + connector: connector_, + method: "claimRewards", + args: [market, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await rewardInterface.connect(signer).balanceOf(dsaWallet0.address)).toFixed(0)).to.be.gte( + amt + ); + }); + + it("Should supply USDC in Compound V3 through dsaWallet0", async function () { + const amount = ethers.utils.parseUnits("100", 6); // 1 ETH + const spells = [ + { + connector: "COMPOUND-V3-TEST-A", + method: "deposit", + args: [market, base, amount, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await baseContract.connect(signer).balanceOf(dsaWallet0.address)).toFixed(0)).to.be.lte( + ethers.utils.parseUnits("0", 6) + ); + expect(new BigNumber(await comet.connect(signer).balanceOf(dsaWallet0.address)).toFixed(0)).to.be.gte( + ethers.utils.parseUnits("499", 6) + ); + }); + + it("Should claim rewards to dsa1", async function () { + let reward = (await cometReward.connect(signer).rewardConfig(market)).token; + let rewardInterface = new ethers.Contract(reward, ABI); + let owed_ = await cometReward.connect(signer).callStatic.getRewardOwed(market, dsaWallet0.address); + let amt: number = owed_.owed; + + const spells = [ + { + connector: connector_, + method: "claimRewardsOnBehalfOf", + args: [market, dsaWallet0.address, dsaWallet1.address, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await rewardInterface.connect(signer).balanceOf(dsaWallet1.address)).toFixed(0)).to.be.gte( + amt + ); + }); + + it("should allow manager for dsaWallet0's collateral and base", async function () { + const spells = [ + { + connector: connectorName, + method: "toggleAccountManager", + args: [market, dsaWallet1.address, true] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + }); + + it("Should claim rewards to dsa1 using manager", async function () { + let reward = (await cometReward.connect(signer).rewardConfig(market)).token; + let rewardInterface = new ethers.Contract(reward, ABI); + let owed_ = await cometReward.connect(signer).callStatic.getRewardOwed(market, dsaWallet0.address); + let amt: number = owed_.owed; + + const spells = [ + { + connector: connector_, + method: "claimRewardsOnBehalfOf", + args: [market, dsaWallet0.address, dsaWallet1.address, 0] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await rewardInterface.connect(signer).balanceOf(dsaWallet1.address)).toFixed(0)).to.be.gte( + amt + ); + }); + }); +}); diff --git a/test/polygon/compound/compound.iii.test.ts b/test/polygon/compound/compound.iii.test.ts new file mode 100644 index 00000000..ca490f66 --- /dev/null +++ b/test/polygon/compound/compound.iii.test.ts @@ -0,0 +1,661 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { Signer, Contract } from "ethers"; +import { BigNumber } from "bignumber.js"; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { tokens, tokenMapping } from "../../../scripts/tests/polygon/tokens"; +import { abis } from "../../../scripts/constant/abis"; +import { constants } from "../../../scripts/constant/constant"; +import { ConnectV2CompoundV3Polygon__factory } from "../../../typechain"; +import { MaxUint256 } from "@uniswap/sdk-core"; + +describe("Compound III", async function () { + const connectorName = "COMPOUND-POLYGON-V3-TEST-A"; + const market = "0xF25212E676D1F7F89Cd72fFEe66158f541246445"; + const base = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"; + const account = "0xF25212E676D1F7F89Cd72fFEe66158f541246445"; + const wethWhale = "0x06959153B974D0D5fDfd87D561db6d8d4FA0bb0B"; + + const ABI = [ + "function balanceOf(address account) public view returns (uint256)", + "function approve(address spender, uint256 amount) external returns(bool)", + "function transfer(address recipient, uint256 amount) external returns (bool)" + ]; + let wmaticContract: any; + let baseContract: any; + + const cometABI = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "borrowBalanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseBorrowMin", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseMinForRewards", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseToken", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "priceFeed", type: "address" }], + name: "getPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "manager", type: "address" } + ], + name: "hasPermission", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "numAssets", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "baseAmount", type: "uint256" } + ], + name: "quoteCollateral", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "userBasic", + outputs: [ + { internalType: "int104", name: "principal", type: "int104" }, + { internalType: "uint64", name: "baseTrackingIndex", type: "uint64" }, + { internalType: "uint64", name: "baseTrackingAccrued", type: "uint64" }, + { internalType: "uint16", name: "assetsIn", type: "uint16" }, + { internalType: "uint8", name: "_reserved", type: "uint8" } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" } + ], + name: "userCollateral", + outputs: [ + { internalType: "uint128", name: "balance", type: "uint128" }, + { internalType: "uint128", name: "_reserved", type: "uint128" } + ], + stateMutability: "view", + type: "function" + } + ]; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let dsaWallet3: any; + let wallet: any; + let dsa0Signer: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + let signer: any; + let wethSigner: any; + + let comet: any; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + //@ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + // blockNumber: 41860206 + } + } + ] + }); + masterSigner = await getMasterSigner(); + await hre.network.provider.send("hardhat_setBalance", [ + await masterSigner.getAddress(), + "0x3635c9adc5de9fffff", + ]); + + signer = await ethers.getSigner(account); + + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2CompoundV3Polygon__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + + await hre.network.provider.send("hardhat_setBalance", [account, ethers.utils.parseEther("10").toHexString()]); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [account] + }); + + signer = await ethers.getSigner(account); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wethWhale] + }); + wethSigner = await ethers.getSigner(wethWhale); + baseContract = new ethers.Contract(base, ABI, wethSigner) + wmaticContract = new ethers.Contract(tokens.wmatic.address, ABI, wethSigner) + comet = new ethers.Contract(market, cometABI, wethSigner) + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + dsaWallet1 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet1.address).to.be.true; + dsaWallet2 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet2.address).to.be.true; + dsaWallet3 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet3.address).to.be.true; + wallet = await ethers.getSigner(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Deposit Matic into DSA wallet", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wallet.address] + }); + + dsa0Signer = await ethers.getSigner(wallet.address); + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("1000") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1000")); + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("1000") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1000")); + await wallet0.sendTransaction({ + to: dsaWallet3.address, + value: ethers.utils.parseEther("1000") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1000")); + }); + }); + + describe("Main", function () { + //deposit asset + it("Should supply Matic collateral in Compound V3", async function () { + const amount = ethers.utils.parseEther("500"); // 5 Matic + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [market, tokens.matic.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte(ethers.utils.parseEther("500")); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("500") + ); + }); + + //deposit asset on behalf of + it("Should supply Matic collateral on behalf of dsaWallet0 in Compound V3", async function () { + const amount = ethers.utils.parseEther("100"); // 100 Matic + const spells = [ + { + connector: connectorName, + method: "depositOnBehalf", + args: [market, tokens.matic.address, dsaWallet0.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(ethers.utils.parseEther("900")); + expect((await comet.connect(wallet0).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("600") + ); + }); + + it("Should borrow and payback base token from Compound", async function () { + const amount = ethers.utils.parseUnits("150", 6); + const spells = [ + { + connector: connectorName, + method: "borrow", + args: [market, base, amount, 0, 0] + }, + { + connector: connectorName, + method: "payback", + args: [market, base, ethers.utils.parseUnits("50", 6), 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await comet.connect(wallet0).borrowBalanceOf(dsaWallet0.address)).to.be.equal( + ethers.utils.parseUnits("100", 6) + ); + expect(await baseContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.equal( + ethers.utils.parseUnits("100", 6) + ); + }); + + it("should allow manager for dsaWallet0's collateral and base", async function () { + const spells = [ + { + connector: connectorName, + method: "toggleAccountManager", + args: [market, dsaWallet2.address, true] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + }); + + it("should payback base token on Compound using manager", async function () { + await baseContract.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseUnits("5", 6)); + + const amount = ethers.utils.parseUnits("102", 6); + await baseContract.connect(dsa0Signer).approve(market, amount); + + const spells = [ + { + connector: connectorName, + method: "paybackFromUsingManager", + args: [market, base, dsaWallet0.address, dsaWallet0.address, ethers.constants.MaxUint256, 0, 0] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await comet.connect(signer).borrowBalanceOf(dsaWallet0.address)).to.be.equal( + ethers.utils.parseUnits("0", 6) + ); + }); + + it("Should borrow to another dsa from Compound", async function () { + const amount = ethers.utils.parseUnits("100", 6); + const spells = [ + { + connector: connectorName, + method: "borrowTo", + args: [market, base, dsaWallet1.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await comet.connect(signer).borrowBalanceOf(dsaWallet0.address)).toFixed()).to.be.equal( + ethers.utils.parseUnits("100", 6) + ); + }); + + it("Should payback on behalf of from Compound", async function () { + const spells = [ + { + connector: connectorName, + method: "paybackOnBehalf", + args: [market, base, dsaWallet0.address, ethers.constants.MaxUint256, 0, 0] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await comet.connect(signer).borrowBalanceOf(dsaWallet0.address)).to.be.equal( + ethers.utils.parseUnits("0", 6) + ); + }); + + it("should withdraw some Matic collateral", async function () { + let initialBal = await ethers.provider.getBalance(dsaWallet0.address); + const amount_ = ethers.utils.parseEther("200"); + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [market, tokens.matic.address, amount_, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("400") + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(initialBal.add(amount_).toString()); + }); + + it("manager should be able to withdraw collateral from the position and transfer", async function () { + await wallet1.sendTransaction({ + to: tokens.wmatic.address, + value: ethers.utils.parseEther("1000") + }); + const amount = ethers.constants.MaxUint256; + const spells = [ + { + connector: connectorName, + method: "withdrawOnBehalfAndTransfer", + args: [market, tokens.matic.address, dsaWallet0.address, dsaWallet1.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("0") + ); + expect(await wmaticContract.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("400")); + }); + + it("Should withdraw collateral to another DSA", async function () { + const spells1 = [ + { + connector: connectorName, + method: "deposit", + args: [market, tokens.matic.address, ethers.utils.parseEther("500"), 0, 0] + } + ]; + + const tx1 = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + let initialBal = await ethers.provider.getBalance(dsaWallet0.address); + + const amount = ethers.utils.parseEther("200"); + const spells = [ + { + connector: connectorName, + method: "withdrawTo", + args: [market, tokens.matic.address, dsaWallet0.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await wmaticContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte(amount); + + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("300") + ); + }); + + it("Should withdraw collateral to another DSA", async function () { + const spells1 = [ + { + connector: connectorName, + method: "deposit", + args: [market, tokens.matic.address, ethers.utils.parseEther("300"), 0, 0] + } + ]; + + const tx1 = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + let initialBal = await ethers.provider.getBalance(dsaWallet0.address); + + const amount = ethers.utils.parseEther("200"); + const spells = [ + { + connector: connectorName, + method: "withdrawTo", + args: [market, tokens.matic.address, dsaWallet0.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(initialBal.add(amount)); + + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("100") + ); + }); + + it("should transfer matic from dsaWallet1 to dsaWallet0 position", async function () { + const spells = [ + { + connector: connectorName, + method: "transferAsset", + args: [market, tokens.matic.address, dsaWallet0.address, ethers.utils.parseEther("300"), 0, 0] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("0") + ); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("300") + ); + }); + + it("should transfer base token from dsaWallet1 to dsaWallet0 position", async function () { + await baseContract.connect(wethSigner).transfer(dsaWallet1.address, ethers.utils.parseUnits("1000", 6)); + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [market, base, ethers.constants.MaxUint256, 0, 0] + } + ]; + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + let initialBal = await baseContract.connect(signer).balanceOf(dsaWallet1.address); + let spells1 = [ + { + connector: connectorName, + method: "transferAsset", + args: [market, base, dsaWallet0.address, ethers.constants.MaxUint256, 0, 0] + } + ]; + + const tx1 = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + const receipt1 = await tx.wait(); + expect(await comet.connect(signer).balanceOf(dsaWallet1.address)).to.be.lte(ethers.utils.parseUnits("0", 6)); + expect(await comet.connect(signer).balanceOf(dsaWallet0.address)).to.be.gte(initialBal); + }); + + it("should transfer base token using manager from dsaWallet0 to dsaWallet1 position", async function () { + const spells = [ + { + connector: connectorName, + method: "transferAssetOnBehalf", + args: [market, base, dsaWallet0.address, dsaWallet1.address, ethers.constants.MaxUint256, 0, 0] + } + ]; + let initialBal = await baseContract.connect(signer).balanceOf(dsaWallet0.address); + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(await comet.connect(signer).balanceOf(dsaWallet0.address)).to.be.lte(ethers.utils.parseUnits("0", 6)); + expect(await comet.connect(signer).balanceOf(dsaWallet1.address)).to.be.gte(initialBal); + }); + + it("should deposit weth using manager", async function () { + await wmaticContract.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseEther("1000")); + let initialBal = await wmaticContract.connect(wallet0).balanceOf(dsaWallet0.address); + + const amount = ethers.utils.parseEther("100"); + await wmaticContract.connect(dsa0Signer).approve(market, amount); + + const spells = [ + { + connector: connectorName, + method: "depositFromUsingManager", + args: [market, tokens.matic.address, dsaWallet0.address, dsaWallet1.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance).to.be.gte( + ethers.utils.parseEther("100") + ); + expect(await wmaticContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.lte(initialBal.sub(amount)); + }); + + it("should allow manager for dsaWallet0's collateral", async function () { + const spells = [ + { + connector: connectorName, + method: "toggleAccountManager", + args: [market, dsaWallet2.address, true] + } + ]; + + const tx = await dsaWallet3.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + }); + it("should borrow on behalf using manager", async function () { + let initialBal = await baseContract.connect(wallet0).balanceOf(dsaWallet0.address); + await wallet0.sendTransaction({ + to: dsaWallet3.address, + value: ethers.utils.parseEther("1500") + }); + const spells1 = [ + { + connector: connectorName, + method: "deposit", + args: [market, tokens.matic.address, ethers.utils.parseEther("1500"), 0, 0] + } + ]; + const tx1 = await dsaWallet3.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + const amount = ethers.utils.parseUnits("500", 6); + const spells = [ + { + connector: connectorName, + method: "borrowOnBehalfAndTransfer", + args: [market, base, dsaWallet3.address, dsaWallet0.address, amount, 0, 0] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect(new BigNumber(await comet.connect(signer).borrowBalanceOf(dsaWallet3.address)).toFixed()).to.be.equal( + ethers.utils.parseUnits("500", 6) + ); + expect(await baseContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.equal(initialBal.add(amount)); + }); + + it("should transferAsset collateral using manager", async function () { + let bal1 = (await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance; + let bal0 = (await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance; + const spells = [ + { + connector: connectorName, + method: "transferAssetOnBehalf", + args: [market, tokens.matic.address, dsaWallet0.address, dsaWallet1.address, ethers.utils.parseEther("100"), 0, 0] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.wmatic.address)).balance).to.be.gte( + bal1.add(ethers.utils.parseEther("100")).toString() + ); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.wmatic.address)).balance).to.be.gte( + bal0.sub(ethers.utils.parseEther("100")).toString() + ); + }); + + //can buy only when target reserves not reached. + + // it("should buy collateral", async function () { + // //deposit 10 usdc(base token) to dsa + // await baseContract.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseUnits("10", 6)); + // console.log(await baseContract.connect(signer).balanceOf(dsaWallet0.address)); + + // //dsawallet0 --> collateral 0eth, balance 9eth 10usdc + // //dsaWallet1 --> balance 2eth coll: 3eth + // const amount = ethers.utils.parseUnits("1",6); + // const bal = await baseContract.connect(signer).balanceOf(dsaWallet0.address); + // const spells = [ + // { + // connector: connectorName, + // method: "buyCollateral", + // args: [market, tokens.link.address, dsaWallet0.address, amount, bal, 0, 0] + // } + // ]; + + // const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + // const receipt = await tx.wait(); + // expect(new BigNumber(await linkContract.connect(signer).balanceOf(dsaWallet0.address)).toFixed()).to.be.gte( + // ethers.utils.parseEther("100") + // ); + + // //dsawallet0 --> collateral 0eth, balance 9eth >1link + // //dsaWallet1 --> balance 2eth coll: 3eth + // }); + }); +});