diff --git a/contracts/mainnet/connectors/compound-iii/events.sol b/contracts/mainnet/connectors/compound-iii/events.sol index 83cd44c7..ef317e17 100644 --- a/contracts/mainnet/connectors/compound-iii/events.sol +++ b/contracts/mainnet/connectors/compound-iii/events.sol @@ -140,4 +140,12 @@ contract Events { uint256 s, bool allow ); + + event LogApproveMarket( + address indexed market, + address indexed token, + uint256 indexed amount, + uint256 getId, + uint256 setId + ); } diff --git a/contracts/mainnet/connectors/compound-iii/helpers.sol b/contracts/mainnet/connectors/compound-iii/helpers.sol index c8211216..b416e40d 100644 --- a/contracts/mainnet/connectors/compound-iii/helpers.sol +++ b/contracts/mainnet/connectors/compound-iii/helpers.sol @@ -56,10 +56,10 @@ abstract contract Helpers is DSMath, Basic { } } - function _borrowOrWithdraw(BorrowWithdrawParams memory params) - internal - returns (uint256 amt, uint256 setId) - { + function _borrowOrWithdraw( + BorrowWithdrawParams memory params, + bool isWithdraw + ) internal returns (uint256 amt, uint256 setId) { uint256 amt_ = getUint(params.getId, params.amt); require( @@ -79,6 +79,29 @@ abstract contract Helpers is DSMath, Basic { amt_ = amt_ == uint256(-1) ? initialBal : amt_; + if (isWithdraw) { + if (token_ == getBaseToken(params.market)) { + //from address is address(this) for withdrawOnBehalf + params.from = params.from == address(0) + ? address(this) + : params.from; + uint256 balance = CometInterface(params.market).balanceOf(params.from); + if (balance > 0) { + require( + amt_ <= balance, + "withdraw-amt-greater-than-supplies" + ); + } + } + } else { + params.from = params.from == address(0) + ? address(this) + : params.from; + uint256 balance = CometInterface(params.market).balanceOf(params.from); + + require(balance == 0, "borrow-disabled-when-supplied-base"); + } + _withdraw(params.market, token_, params.from, params.to, amt_); uint256 finalBal = getAccountSupplyBalanceOfAsset( diff --git a/contracts/mainnet/connectors/compound-iii/main.sol b/contracts/mainnet/connectors/compound-iii/main.sol index f1b388db..ede519ef 100644 --- a/contracts/mainnet/connectors/compound-iii/main.sol +++ b/contracts/mainnet/connectors/compound-iii/main.sol @@ -45,6 +45,12 @@ abstract contract CompoundV3Resolver is Events, Helpers { address token_ = isEth ? wethAddr : token; TokenInterface tokenContract = TokenInterface(token_); + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(address(this)) == 0, + "debt-not-repaid" + ); + } if (isEth) { amt_ = amt_ == uint256(-1) ? address(this).balance : amt_; convertEthToWeth(isEth, tokenContract, amt_); @@ -96,6 +102,13 @@ abstract contract CompoundV3Resolver is Events, Helpers { address token_ = isEth ? wethAddr : token; TokenInterface tokenContract = TokenInterface(token_); + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(to) == 0, + "debt-not-repaid" + ); + } + if (isEth) { amt_ = amt_ == uint256(-1) ? address(this).balance : amt_; convertEthToWeth(isEth, tokenContract, amt_); @@ -149,6 +162,13 @@ abstract contract CompoundV3Resolver is Events, Helpers { address token_ = isEth ? wethAddr : token; TokenInterface tokenContract = TokenInterface(token_); + if (token_ == getBaseToken(market)) { + require( + CometInterface(market).borrowBalanceOf(to) == 0, + "debt-not-repaid" + ); + } + amt_ = setAmt(market, token_, from, amt_, isEth); CometInterface(market).supplyFrom(from, to, token_, amt_); @@ -198,6 +218,13 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt_ = amt_ == uint256(-1) ? initialBal : amt_; + if (token_ == getBaseToken(market)) { + uint256 balance = CometInterface(market).balanceOf(address(this)); + if (balance > 0) { + require(amt_ <= balance, "withdraw-amt-greater-than-supplies"); + } + } + CometInterface(market).withdraw(token_, amt_); uint256 finalBal = getAccountSupplyBalanceOfAsset( @@ -247,7 +274,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt: amt, getId: getId, setId: setId - }) + }), + true ); eventName_ = "LogWithdrawOnBehalf(address,address,address,uint256,uint256,uint256)"; @@ -287,7 +315,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt: amt, getId: getId, setId: setId - }) + }), + true ); eventName_ = "LogWithdrawFromUsingManager(address,address,address,address,uint256,uint256,uint256)"; @@ -330,6 +359,11 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt_ = amt_ == uint256(-1) ? initialBal : amt_; + if (token_ == getBaseToken(market)) { + uint256 balance = CometInterface(market).balanceOf(address(this)); + require(balance == 0, "borrow-disabled-when-supplied-base"); + } + CometInterface(market).withdraw(token_, amt_); uint256 finalBal = getAccountSupplyBalanceOfAsset( @@ -377,7 +411,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt: amt, getId: getId, setId: setId - }) + }), + false ); eventName_ = "LogBorrowOnBehalf(address,address,uint256,uint256,uint256)"; eventParam_ = abi.encode(market, to, amt_, getId, setId_); @@ -414,7 +449,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { amt: amt, getId: getId, setId: setId - }) + }), + false ); eventName_ = "LogBorrowFromUsingManager(address,address,address,uint256,uint256,uint256)"; eventParam_ = abi.encode(market, from, to, amt_, getId, setId_); @@ -450,6 +486,13 @@ abstract contract CompoundV3Resolver is Events, Helpers { ? TokenInterface(market).balanceOf(address(this)) : amt_; + uint256 borrowBal = CometInterface(market).borrowBalanceOf( + address(this) + ); + if (borrowBal > 0) { + require(amt_ <= borrowBal, "repay-amt-greater-than-debt"); + } + if (isEth) { convertEthToWeth(isEth, tokenContract, amt_); } @@ -495,6 +538,11 @@ abstract contract CompoundV3Resolver is Events, Helpers { ? TokenInterface(market).balanceOf(to) : amt_; + uint256 borrowBal = CometInterface(market).borrowBalanceOf(to); + if (borrowBal > 0) { + require(amt_ <= borrowBal, "repay-amt-greater-than-debt"); + } + if (isEth) { convertEthToWeth(isEth, tokenContract, amt_); } @@ -511,7 +559,7 @@ abstract contract CompoundV3Resolver is Events, Helpers { /** * @dev Repays entire borrow of the base asset form 'from' on behalf of 'to'. - * @notice Repays an entire borrow of the base asset on behalf of 'to'. Approve the comet markey + * @notice Repays an entire borrow of the base asset on behalf of 'to'. Approve the comet markey * @param market The address of the market. * @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. @@ -540,6 +588,18 @@ abstract contract CompoundV3Resolver is Events, Helpers { TokenInterface tokenContract = TokenInterface(token_); amt_ = setAmt(market, token_, from, amt_, isEth); + + uint256 borrowBal = CometInterface(market).borrowBalanceOf(to); + if (borrowBal > 0) { + require(amt_ <= borrowBal, "repay-amt-greater-than-debt"); + } + + if (isEth) { + convertEthToWeth(isEth, tokenContract, amt_); + } + + approve(tokenContract, market, amt_); + CometInterface(market).supplyFrom(from, to, token_, amt_); setUint(setId, amt_); @@ -582,6 +642,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { convertEthToWeth(isEth, tokenContract, amt_); } + approve(tokenContract, market, amt_); + CometInterface(market).buyCollateral( asset, minCollateralAmt, @@ -605,8 +667,8 @@ abstract contract CompoundV3Resolver is Events, Helpers { } /** - * @dev Transfer collateral or base asset to dest address from this account. - * @notice Transfer collateral asset to dest address from caller's account. + * @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. @@ -763,6 +825,40 @@ abstract contract CompoundV3Resolver is Events, Helpers { s ); } + + function approveMarket( + 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" + ); + require(amt > 0, "amount-cannot-be-zero"); + + bool isEth = token == ethAddr; + address token_ = isEth ? wethAddr : token; + TokenInterface tokenContract = TokenInterface(token_); + + amt_ = amt_ == uint256(-1) + ? TokenInterface(market).balanceOf(address(this)) + : amt_; + + approve(tokenContract, market, amt_); + + setUint(setId, amt_); + + eventName_ = "LogApproveMarket(address,address,uint256,uint256,uint256)"; + eventParam_ = abi.encode(market, token, amt_, getId, setId); + } } contract ConnectV2CompoundV3 is CompoundV3Resolver { diff --git a/test/mainnet/compound/compound.iii.test.ts b/test/mainnet/compound/compound.iii.test.ts index 342c7232..7a21a0f3 100644 --- a/test/mainnet/compound/compound.iii.test.ts +++ b/test/mainnet/compound/compound.iii.test.ts @@ -136,6 +136,8 @@ describe("Compound III", function () { let dsaWallet0: any; let dsaWallet1: any; let dsaWallet2: any; + let wallet: any; + let dsa0Signer: any; let masterSigner: Signer; let instaConnectorsV2: Contract; let connector: any; @@ -193,9 +195,17 @@ describe("Compound III", function () { expect(!!dsaWallet1.address).to.be.true; dsaWallet2 = await buildDSAv2(wallet0.address); expect(!!dsaWallet2.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") @@ -247,6 +257,8 @@ describe("Compound III", function () { expect((await comet.connect(wallet0).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( ethers.utils.parseEther("6") ); + //dsawallet0 --> collateral 6eth, balance 5eth + //dsaWallet1 --> balance 9eth coll: 0eth }); it("Should borrow and payback base token from Compound", async function () { @@ -269,18 +281,34 @@ describe("Compound III", function () { expect(await comet.connect(wallet0).borrowBalanceOf(dsaWallet0.address)).to.be.equal( ethers.utils.parseUnits("100", 6) ); - console.log(baseContract); expect(await baseContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.equal( ethers.utils.parseUnits("100", 6) ); }); - it("should payback base token from Compound", async function () { + it("should allow manager for dsaWallet0's collateral", async function () { const spells = [ { connector: connectorName, - method: "payback", - args: [market, ethers.constants.MaxUint256, 0, 0] + 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 from Compound", async function () { + const amount = ethers.utils.parseUnits("100", 6); + //approve market to access dsaWallet0 + await baseContract.connect(dsa0Signer).approve(market, amount); + + const spells = [ + { + connector: connectorName, + method: "paybackFromUsingManager", + args: [market, dsaWallet0.address, dsaWallet0.address, ethers.constants.MaxUint256, 0, 0] } ]; @@ -313,6 +341,8 @@ describe("Compound III", function () { expect(new BigNumber(await comet.connect(signer).borrowBalanceOf(dsaWallet0.address)).toFixed()).to.be.equal( ethers.utils.parseUnits("100", 6) ); + //dsawallet0 --> collateral 6eth, balance 5eth, 100usdc + //dsaWallet1 --> balance 3eth coll: 0eth }); it("Should payback on behalf of from Compound", async function () { @@ -329,19 +359,9 @@ describe("Compound III", function () { expect(await comet.connect(signer).borrowBalanceOf(dsaWallet0.address)).to.be.equal( ethers.utils.parseUnits("0", 6) ); - }); - it("should allow manager for dsaWallet0's collateral", 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(); + //dsawallet0 --> collateral 6eth, balance 5eth + //dsaWallet1 --> balance 3eth coll: 0eth }); it("should withdraw some ETH collateral", async function () { @@ -359,7 +379,7 @@ describe("Compound III", function () { expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( ethers.utils.parseEther("4") ); - expect(await wethContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("2")); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("7")); }); it("manager should be able to withdraw collateral from the position", async function () { @@ -367,7 +387,7 @@ describe("Compound III", function () { const spells = [ { connector: connectorName, - method: "withdrawFrom", + method: "withdrawFromUsingManager", args: [market, tokens.eth.address, dsaWallet0.address, dsaWallet1.address, amount, 0, 0] } ]; @@ -377,8 +397,10 @@ describe("Compound III", function () { expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( ethers.utils.parseEther("0") ); - expect(await wethContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("2")); - expect(await wethContract.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("5")); + + //dsawallet0 --> collateral 0eth, balance 7eth + //dsaWallet1 --> balance 7eth coll: 0eth + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("7")); }); it("Should withdraw on behalf of from Compound", async function () { @@ -389,27 +411,39 @@ describe("Compound III", function () { args: [market, tokens.eth.address, ethers.utils.parseEther("5"), 0, 0] } ]; + //dsawallet0 --> collateral 0eth, balance 7eth + //dsaWallet1 --> balance 2eth coll: 5eth const tx1 = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + const amount = ethers.utils.parseEther("2"); const spells = [ { connector: connectorName, method: "withdrawOnBehalf", - args: [market, dsaWallet1.address, amount, 0, 0] + args: [market, dsaWallet0.address, amount, 0, 0] } ]; + //dsawallet0 --> collateral 0eth, balance 9eth + //dsaWallet1 --> balance 2eth coll: 3eth - const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); const receipt = await tx.wait(); - expect(new BigNumber(await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.eth.address).balance).toFixed()).to.be.equal( - ethers.utils.parseEther("5") - ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("9")); + + expect( + new BigNumber( + await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.eth.address).balance + ).toFixed() + ).to.be.equal(ethers.utils.parseEther("3")); }); it("should buy collateral", async function () { //deposit 10 usdc(base token) to dsa - await baseContract.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseUnits("10", 0)); + await baseContract.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseUnits("10", 6)); + + //dsawallet0 --> collateral 0eth, balance 9eth 10usdc + //dsaWallet1 --> balance 2eth coll: 3eth const amount = ethers.utils.parseEther("1"); const bal = await baseContract.connect(signer).balanceOf(dsaWallet0.address); const spells = [ @@ -425,6 +459,9 @@ describe("Compound III", function () { expect(new BigNumber(await linkContract.connect(signer).balanceOf(dsaWallet0.address)).toFixed()).to.be.gte( ethers.utils.parseEther("1") ); + + //dsawallet0 --> collateral 0eth, balance 9eth >1link + //dsaWallet1 --> balance 2eth coll: 3eth }); it("should transfer eth from dsaWallet1 to dsaWallet0 position", async function () { @@ -438,17 +475,92 @@ describe("Compound III", function () { const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); const receipt = await tx.wait(); - expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( + expect((await comet.connect(signer).userCollateral(dsaWallet1.address, tokens.weth.address)).balance).to.be.gte( ethers.utils.parseEther("0") ); expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( - ethers.utils.parseEther("0") + ethers.utils.parseEther("3") ); - }) - //depositFromUsing --> approve first - //paybackUsingManager --> approve first - //borrow using manager - //transferAssetUsingManager + //dsawallet0 --> collateral 3eth, balance 9eth >1link + //dsaWallet1 --> balance 2eth coll: 0eth + }); + + it("should deposit link from using manager", async function () { + const amount = ethers.utils.parseEther("1"); + //approve market to access dsaWallet0 + await baseContract.connect(dsa0Signer).approve(market, amount); + + const spells = [ + { + connector: connectorName, + method: "depositFromUsingManager", + args: [market, dsaWallet0.address, dsaWallet1.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(dsaWallet1.address, tokens.link.address)).balance).to.be.gte( + ethers.utils.parseEther("1") + ); + + //dsawallet0 --> collateral 3eth, balance 9eth previous-1link + //dsaWallet1 --> balance 2eth coll: 0eth,1link + }); + + it("should borrow using manager", async function () { + const spells1 = [ + { + connector: connectorName, + method: "deposit", + args: [market, tokens.eth.address, ethers.utils.parseEther("6"), 0, 0] + } + ]; + + const tx1 = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells1), wallet1.address); + const amount = ethers.utils.parseUnits("50", 6); + const spells = [ + { + connector: connectorName, + method: "borrowFromUsingManager", + args: [market, dsaWallet0.address, dsaWallet1.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(dsaWallet0.address)).toFixed()).to.be.equal( + ethers.utils.parseUnits("50", 6) + ); + expect(await baseContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.equal( + ethers.utils.parseUnits("50", 6) + ); + + //dsawallet0 --> collateral 9eth, balance 3eth previous-1link borrowed 50usdc(transferred to dsa1) + //dsaWallet1 --> balance 2eth coll: 0eth,1link, 50usdc + }); + + it("should transferAsset using manager", async function () { + const spells = [ + { + connector: connectorName, + method: "transferAssetFromUsingManager", + args: [market, tokens.eth.address, dsaWallet0.address, dsaWallet1.address, ethers.utils.parseEther("1"), 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.weth.address)).balance).to.be.gte( + ethers.utils.parseEther("3") + ); + expect((await comet.connect(signer).userCollateral(dsaWallet0.address, tokens.weth.address)).balance).to.be.gte( + ethers.utils.parseEther("8") + ); + + //dsawallet0 --> collateral 8eth, balance 3eth previous-1link borrowed 50usdc(transferred to dsa1) + //dsaWallet1 --> balance 3eth coll: 0eth,1link, 50usdc + }); }); });