-
Notifications
You must be signed in to change notification settings - Fork 79
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 #194 from 1inch/feature/erc4626wrapper
[SC-1288][SC-1290] Erc4626 Wrapper
- Loading branch information
Showing
4 changed files
with
167 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.23; | ||
|
||
import "../interfaces/IWrapper.sol"; | ||
import "@openzeppelin/contracts/interfaces/IERC4626.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
contract Erc4626Wrapper is IWrapper, Ownable { | ||
mapping(IERC20 => IERC20) public assosiatedToken; | ||
|
||
constructor(address _owner) Ownable(_owner) {} // solhint-disable-line no-empty-blocks | ||
|
||
mapping(IERC20 => IERC20) public baseToWbase; | ||
mapping(IERC20 => IERC20) public wbaseToBase; | ||
|
||
function addMarkets(address[] memory tokens) external onlyOwner { | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
IERC20 baseToken = IERC20(IERC4626(tokens[i]).asset()); | ||
wbaseToBase[IERC20(tokens[i])] = baseToken; | ||
baseToWbase[baseToken] = IERC20(tokens[i]); | ||
} | ||
} | ||
|
||
function removeMarkets(address[] memory tokens) external onlyOwner { | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
IERC20 baseToken = IERC20(IERC4626(tokens[i]).asset()); | ||
delete wbaseToBase[IERC20(tokens[i])]; | ||
delete baseToWbase[baseToken]; | ||
} | ||
} | ||
|
||
function wrap(IERC20 token) external view override returns (IERC20 wrappedToken, uint256 rate) { | ||
IERC20 base = wbaseToBase[token]; | ||
IERC20 wbase = baseToWbase[token]; | ||
if (base != IERC20(address(0))) { | ||
return (base, IERC4626(address(token)).convertToAssets(1e18)); // scale up when redeeming wbase for base | ||
} else if (wbase != IERC20(address(0))) { | ||
return (wbase, IERC4626(address(wbase)).convertToShares(1e18)); // scale down when minting wbase with base | ||
} else { | ||
revert NotSupportedToken(); | ||
} | ||
} | ||
} |
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,119 @@ | ||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); | ||
const { ethers, network } = require('hardhat'); | ||
const { expect, ether, deployContract, constants } = require('@1inch/solidity-utils'); | ||
const { tokens } = require('../helpers.js'); | ||
const { resetHardhatNetworkFork } = require('@1inch/solidity-utils/hardhat-setup'); | ||
|
||
describe('Erc4626Wrapper', function () { | ||
async function initContracts () { | ||
const [owner, alice] = await ethers.getSigners(); | ||
const erc4626Wrapper = await deployContract('Erc4626Wrapper', [owner]); | ||
return { owner, alice, erc4626Wrapper }; | ||
} | ||
|
||
it('should add market via addMarkets and remove market via removeMarkets', async function () { | ||
const { erc4626Wrapper } = await loadFixture(initContracts); | ||
await erc4626Wrapper.addMarkets([tokens.sUSDe]); | ||
expect(await erc4626Wrapper.wbaseToBase(tokens.sUSDe)).to.equal(tokens.USDe); | ||
expect(await erc4626Wrapper.baseToWbase(tokens.USDe)).to.equal(tokens.sUSDe); | ||
|
||
await erc4626Wrapper.removeMarkets([tokens.sUSDe]); | ||
expect(await erc4626Wrapper.wbaseToBase(tokens.sUSDe)).to.equal(constants.ZERO_ADDRESS); | ||
expect(await erc4626Wrapper.baseToWbase(tokens.USDe)).to.equal(constants.ZERO_ADDRESS); | ||
}); | ||
|
||
it('should not add market by non-owner', async function () { | ||
const { alice, erc4626Wrapper } = await loadFixture(initContracts); | ||
await expect(erc4626Wrapper.connect(alice).addMarkets([tokens.sUSDe])).to.be.revertedWithCustomError(erc4626Wrapper, 'OwnableUnauthorizedAccount'); | ||
}); | ||
|
||
it('should not remove market by non-owner', async function () { | ||
const { alice, erc4626Wrapper } = await loadFixture(initContracts); | ||
await expect(erc4626Wrapper.connect(alice).removeMarkets([tokens.sUSDe])).to.be.revertedWithCustomError(erc4626Wrapper, 'OwnableUnauthorizedAccount'); | ||
}); | ||
|
||
it('should not return result in wrap method for unsupported token', async function () { | ||
const { erc4626Wrapper } = await loadFixture(initContracts); | ||
await expect(erc4626Wrapper.wrap(tokens.WETH)).to.be.revertedWithCustomError(erc4626Wrapper, 'NotSupportedToken'); | ||
}); | ||
|
||
function shouldReturnCorrectPricesAndTokens (fixture) { | ||
it('base -> wbase', async function () { | ||
const { erc4626Wrapper, wbase, base } = await loadFixture(fixture); | ||
const response = await erc4626Wrapper.wrap(base); | ||
expect(response.rate).to.equal(await wbase.convertToShares(ether('1'))); | ||
expect(response.wrappedToken).to.equal(wbase.target); | ||
}); | ||
|
||
it('wbase -> base', async function () { | ||
const { erc4626Wrapper, wbase, base } = await loadFixture(fixture); | ||
const response = await erc4626Wrapper.wrap(wbase); | ||
expect(response.rate).to.equal(await wbase.convertToAssets(ether('1'))); | ||
expect(response.wrappedToken).to.equal(base); | ||
}); | ||
}; | ||
|
||
describe('sUSDe Wrapper', function () { | ||
async function initContractsAndMarket () { | ||
const { erc4626Wrapper } = await initContracts(); | ||
const sUSDe = await ethers.getContractAt('IERC4626', tokens.sUSDe); | ||
await erc4626Wrapper.addMarkets([sUSDe]); | ||
return { erc4626Wrapper, wbase: sUSDe, base: tokens.USDe }; | ||
} | ||
shouldReturnCorrectPricesAndTokens(initContractsAndMarket); | ||
}); | ||
|
||
describe('sDAI Wrapper', function () { | ||
async function initContractsAndMarket () { | ||
const { erc4626Wrapper } = await initContracts(); | ||
const sDai = await ethers.getContractAt('IERC4626', tokens.sDAI); | ||
await erc4626Wrapper.addMarkets([sDai]); | ||
return { erc4626Wrapper, wbase: sDai, base: tokens.DAI }; | ||
} | ||
shouldReturnCorrectPricesAndTokens(initContractsAndMarket); | ||
}); | ||
|
||
describe('xrETH Wrapper', function () { | ||
async function initContractsAndMarket () { | ||
const { erc4626Wrapper } = await initContracts(); | ||
const xrETH = await ethers.getContractAt('IERC4626', tokens.xrETH); | ||
await erc4626Wrapper.addMarkets([xrETH]); | ||
return { erc4626Wrapper, wbase: xrETH, base: tokens.WETH }; | ||
} | ||
|
||
// Switch off console.log to avoid spamming the console with the logs from the xrETH contract | ||
const originalConsoleLog = console.log; | ||
before(async function () { console.log = function () {}; }); | ||
after(async function () { console.log = originalConsoleLog; }); | ||
|
||
shouldReturnCorrectPricesAndTokens(initContractsAndMarket); | ||
}); | ||
|
||
describe('scrvUSD Wrapper', function () { | ||
async function initContractsAndMarket () { | ||
const { erc4626Wrapper } = await initContracts(); | ||
const scrvUSD = await ethers.getContractAt('IERC4626', tokens.scrvUSD); | ||
await erc4626Wrapper.addMarkets([scrvUSD]); | ||
return { erc4626Wrapper, wbase: scrvUSD, base: tokens.crvUSD }; | ||
} | ||
shouldReturnCorrectPricesAndTokens(initContractsAndMarket); | ||
}); | ||
|
||
describe('wsuperOETHb Wrapper', function () { | ||
before(async function () { | ||
await resetHardhatNetworkFork(network, 'base'); | ||
}); | ||
|
||
after(async function () { | ||
await resetHardhatNetworkFork(network, 'mainnet'); | ||
}); | ||
|
||
async function initContractsAndMarket () { | ||
const { erc4626Wrapper } = await initContracts(); | ||
const wsuperOETHb = await ethers.getContractAt('IERC4626', tokens.base.wsuperOETHb); | ||
await erc4626Wrapper.addMarkets([wsuperOETHb]); | ||
return { erc4626Wrapper, wbase: wsuperOETHb, base: tokens.base.superOETHb }; | ||
} | ||
shouldReturnCorrectPricesAndTokens(initContractsAndMarket); | ||
}); | ||
}); |