-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #121 from 1inch/feature/compound-v3
[SC-1083] CompoundV3 Wrapper
- Loading branch information
Showing
10 changed files
with
1,378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.23; | ||
|
||
// CompoundV3 money market interface | ||
interface IComet { | ||
function baseToken() external view returns (address); | ||
function supply(address asset, uint amount) external; | ||
function withdraw(address asset, uint amount) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.23; | ||
|
||
import "../interfaces/IWrapper.sol"; | ||
import "../interfaces/IComet.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
contract CompoundV3Wrapper is IWrapper, Ownable { | ||
mapping(IERC20 => IERC20) public cTokenToToken; | ||
mapping(IERC20 => IERC20) public tokenTocToken; | ||
|
||
constructor(address _owner) Ownable(_owner) {} // solhint-disable-line no-empty-blocks | ||
|
||
function addMarkets(address[] memory tokens) external onlyOwner { | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
IERC20 baseToken = IERC20(IComet(tokens[i]).baseToken()); | ||
cTokenToToken[IERC20(tokens[i])] = baseToken; | ||
tokenTocToken[baseToken] = IERC20(tokens[i]); | ||
} | ||
} | ||
|
||
function removeMarkets(address[] memory tokens) external onlyOwner { | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
IERC20 baseToken = IERC20(IComet(tokens[i]).baseToken()); | ||
delete cTokenToToken[IERC20(tokens[i])]; | ||
delete tokenTocToken[baseToken]; | ||
} | ||
} | ||
|
||
function wrap(IERC20 token) external view override returns (IERC20 wrappedToken, uint256 rate) { | ||
IERC20 baseToken = cTokenToToken[token]; | ||
IERC20 cToken = tokenTocToken[token]; | ||
if (baseToken != IERC20(address(0))) { | ||
return (baseToken, 1e18); | ||
} else if (cToken != IERC20(address(0))) { | ||
return (cToken, 1e18); | ||
} else { | ||
revert NotSupportedToken(); | ||
} | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
const hre = require('hardhat'); | ||
const { ethers } = hre; | ||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); | ||
const { expect, ether, deployContract, trackReceivedTokenAndTx, assertRoughlyEqualValues, constants } = require('@1inch/solidity-utils'); | ||
const { tokens } = require('../helpers.js'); | ||
|
||
describe('CompoundV3Wrapper', function () { | ||
async function initContracts () { | ||
const [wallet, nonOwner] = await ethers.getSigners(); | ||
const compoundV3Wrapper = await deployContract('CompoundV3Wrapper', [wallet]); | ||
|
||
const IERC20ABI = (await hre.artifacts.readArtifact('IERC20')).abi; | ||
const COMETABI = (await hre.artifacts.readArtifact('IComet')).abi; | ||
const cWETHv3 = new ethers.Contract(tokens.cWETHv3, [...IERC20ABI, ...COMETABI], wallet); | ||
const weth = await ethers.getContractAt('IWETH', tokens.WETH); | ||
|
||
await compoundV3Wrapper.addMarkets([tokens.cWETHv3]); | ||
await weth.deposit({ value: ether('2') }); | ||
await weth.approve(cWETHv3, ether('2')); | ||
|
||
return { wallet, nonOwner, compoundV3Wrapper, weth, cWETHv3 }; | ||
} | ||
|
||
it('should revert when add/remove by non-owner', async function () { | ||
const { nonOwner, compoundV3Wrapper } = await loadFixture(initContracts); | ||
await expect(compoundV3Wrapper.connect(nonOwner).addMarkets([tokens.cUSDCv3])).to.be.revertedWithCustomError(compoundV3Wrapper, 'OwnableUnauthorizedAccount'); | ||
await expect(compoundV3Wrapper.connect(nonOwner).removeMarkets([tokens.cWETHv3])).to.be.revertedWithCustomError(compoundV3Wrapper, 'OwnableUnauthorizedAccount'); | ||
}); | ||
|
||
it('addMarkets', async function () { | ||
const { compoundV3Wrapper } = await loadFixture(initContracts); | ||
await compoundV3Wrapper.addMarkets([tokens.cUSDCv3]); | ||
expect(await compoundV3Wrapper.cTokenToToken(tokens.cUSDCv3)).to.equal(tokens.USDC); | ||
expect(await compoundV3Wrapper.tokenTocToken(tokens.USDC)).to.equal(tokens.cUSDCv3); | ||
}); | ||
|
||
it('removeMarkets', async function () { | ||
const { compoundV3Wrapper } = await loadFixture(initContracts); | ||
await compoundV3Wrapper.removeMarkets([tokens.cWETHv3]); | ||
expect(await compoundV3Wrapper.cTokenToToken(tokens.cWETHv3)).to.equal(constants.ZERO_ADDRESS); | ||
expect(await compoundV3Wrapper.tokenTocToken(tokens.WETH)).to.equal(constants.ZERO_ADDRESS); | ||
}); | ||
|
||
it('WETH -> cWETHv3', async function () { | ||
const { wallet, compoundV3Wrapper, cWETHv3 } = await loadFixture(initContracts); | ||
const response = await compoundV3Wrapper.wrap(tokens.WETH); | ||
const [received] = await trackReceivedTokenAndTx(ethers.provider, cWETHv3, wallet.address, async () => cWETHv3.supply(tokens.WETH, ether('1'))); | ||
expect(response.wrappedToken).to.equal(tokens.cWETHv3); | ||
assertRoughlyEqualValues(response.rate, received, 1e-17); | ||
}); | ||
|
||
it('cWETHv3 -> WETH', async function () { | ||
const { wallet, compoundV3Wrapper, weth, cWETHv3 } = await loadFixture(initContracts); | ||
await cWETHv3.supply(tokens.WETH, ether('2')); | ||
const response = await compoundV3Wrapper.wrap(tokens.cWETHv3); | ||
const [received] = await trackReceivedTokenAndTx(ethers.provider, weth, wallet.address, async () => cWETHv3.withdraw(tokens.WETH, ether('1'))); | ||
expect(response.wrappedToken).to.equal(tokens.WETH); | ||
assertRoughlyEqualValues(response.rate, received, 1e-17); | ||
}); | ||
}); |