Skip to content

Commit

Permalink
Merge pull request #194 from 1inch/feature/erc4626wrapper
Browse files Browse the repository at this point in the history
[SC-1288][SC-1290] Erc4626 Wrapper
  • Loading branch information
zZoMROT authored Nov 14, 2024
2 parents 8c73509 + 97e939b commit 76fa30f
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 2 deletions.
44 changes: 44 additions & 0 deletions contracts/wrappers/Erc4626Wrapper.sol
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();
}
}
}
2 changes: 2 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ const tokens = {
yvWETH: '0xa258C4606Ca8206D8aA700cE2143D7db854D168c',
yvWBTC: '0xA696a63cc78DfFa1a63E9E50587C197387FF6C7E',
crvUSD: '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E',
scrvUSD: '0x0655977FEb2f289A4aB78af67BAB0d17aAb84367',
wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0',
BEAN: '0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab',
'3CRV': '0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490',
stataUSDC: '0x73edDFa87C71ADdC275c2b9890f5c3a8480bC9E6',
stataWETH: '0x252231882FB38481497f3C767469106297c8d93b',
stataDAI: '0xaf270C38fF895EA3f95Ed488CEACe2386F038249',
xrETH: '0xBB22d59B73D7a6F3A8a83A214BECc67Eb3b511fE',
base: {
WETH: '0x4200000000000000000000000000000000000006',
DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
Expand Down
4 changes: 2 additions & 2 deletions test/oracles/SolidlyOracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ describe('SolidlyOracle', function () {
function shouldWorkForOracle (fixture) {
it('WETH -> axlUSDC', async function () {
const { oracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.base.WETH, tokens.base.axlUSDC, tokens.NONE, oracle, uniswapV3Oracle);
await testRate(tokens.base.WETH, tokens.base.axlUSDC, tokens.NONE, oracle, uniswapV3Oracle, 0.1);
});

it('axlUSDC -> WETH', async function () {
const { oracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.base.axlUSDC, tokens.base.WETH, tokens.NONE, oracle, uniswapV3Oracle);
await testRate(tokens.base.axlUSDC, tokens.base.WETH, tokens.NONE, oracle, uniswapV3Oracle, 0.1);
});

it('WETH -> DAI', async function () {
Expand Down
119 changes: 119 additions & 0 deletions test/wrappers/Erc4626Wrapper.js
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);
});
});

0 comments on commit 76fa30f

Please sign in to comment.