From 6af7d302f7d76d3ec7863129560601f688805ce5 Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Tue, 11 Apr 2023 14:49:59 -0400 Subject: [PATCH 1/6] Initial draft zapper --- contracts/contracts/interfaces/IOUSD.sol | 117 ++++++++++++++++++ contracts/contracts/interfaces/ISfrxETH.sol | 127 ++++++++++++++++++++ contracts/contracts/interfaces/IWETH9.sol | 35 ++++++ contracts/contracts/vault/OETHZapper.sol | 57 +++++++++ 4 files changed, 336 insertions(+) create mode 100644 contracts/contracts/interfaces/IOUSD.sol create mode 100644 contracts/contracts/interfaces/ISfrxETH.sol create mode 100644 contracts/contracts/interfaces/IWETH9.sol create mode 100644 contracts/contracts/vault/OETHZapper.sol diff --git a/contracts/contracts/interfaces/IOUSD.sol b/contracts/contracts/interfaces/IOUSD.sol new file mode 100644 index 0000000000..2d9f4bcd2f --- /dev/null +++ b/contracts/contracts/interfaces/IOUSD.sol @@ -0,0 +1,117 @@ +pragma solidity ^0.8.0; + +interface IOUSD { + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + event GovernorshipTransferred( + address indexed previousGovernor, + address indexed newGovernor + ); + event PendingGovernorshipTransfer( + address indexed previousGovernor, + address indexed newGovernor + ); + event TotalSupplyUpdatedHighres( + uint256 totalSupply, + uint256 rebasingCredits, + uint256 rebasingCreditsPerToken + ); + event Transfer(address indexed from, address indexed to, uint256 value); + + function _totalSupply() external view returns (uint256); + + function allowance(address _owner, address _spender) + external + view + returns (uint256); + + function approve(address _spender, uint256 _value) external returns (bool); + + function balanceOf(address _account) external view returns (uint256); + + function burn(address account, uint256 amount) external; + + function changeSupply(uint256 _newTotalSupply) external; + + function claimGovernance() external; + + function creditsBalanceOf(address _account) + external + view + returns (uint256, uint256); + + function creditsBalanceOfHighres(address _account) + external + view + returns ( + uint256, + uint256, + bool + ); + + function decimals() external view returns (uint8); + + function decreaseAllowance(address _spender, uint256 _subtractedValue) + external + returns (bool); + + function governor() external view returns (address); + + function increaseAllowance(address _spender, uint256 _addedValue) + external + returns (bool); + + function initialize( + string memory _nameArg, + string memory _symbolArg, + address _vaultAddress + ) external; + + function isGovernor() external view returns (bool); + + function isUpgraded(address) external view returns (uint256); + + function mint(address _account, uint256 _amount) external; + + function name() external view returns (string memory); + + function nonRebasingCreditsPerToken(address) + external + view + returns (uint256); + + function nonRebasingSupply() external view returns (uint256); + + function rebaseOptIn() external; + + function rebaseOptOut() external; + + function rebaseState(address) external view returns (uint8); + + function rebasingCredits() external view returns (uint256); + + function rebasingCreditsHighres() external view returns (uint256); + + function rebasingCreditsPerToken() external view returns (uint256); + + function rebasingCreditsPerTokenHighres() external view returns (uint256); + + function symbol() external view returns (string memory); + + function totalSupply() external view returns (uint256); + + function transfer(address _to, uint256 _value) external returns (bool); + + function transferFrom( + address _from, + address _to, + uint256 _value + ) external returns (bool); + + function transferGovernance(address _newGovernor) external; + + function vaultAddress() external view returns (address); +} diff --git a/contracts/contracts/interfaces/ISfrxETH.sol b/contracts/contracts/interfaces/ISfrxETH.sol new file mode 100644 index 0000000000..4ff0324ff9 --- /dev/null +++ b/contracts/contracts/interfaces/ISfrxETH.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ISfrxETH { + event Approval( + address indexed owner, + address indexed spender, + uint256 amount + ); + event Deposit( + address indexed caller, + address indexed owner, + uint256 assets, + uint256 shares + ); + event NewRewardsCycle(uint32 indexed cycleEnd, uint256 rewardAmount); + event Transfer(address indexed from, address indexed to, uint256 amount); + event Withdraw( + address indexed caller, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function allowance(address, address) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function asset() external view returns (address); + + function balanceOf(address) external view returns (uint256); + + function convertToAssets(uint256 shares) external view returns (uint256); + + function convertToShares(uint256 assets) external view returns (uint256); + + function decimals() external view returns (uint8); + + function deposit(uint256 assets, address receiver) + external + returns (uint256 shares); + + function depositWithSignature( + uint256 assets, + address receiver, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 shares); + + function lastRewardAmount() external view returns (uint192); + + function lastSync() external view returns (uint32); + + function maxDeposit(address) external view returns (uint256); + + function maxMint(address) external view returns (uint256); + + function maxRedeem(address owner) external view returns (uint256); + + function maxWithdraw(address owner) external view returns (uint256); + + function mint(uint256 shares, address receiver) + external + returns (uint256 assets); + + function name() external view returns (string memory); + + function nonces(address) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function previewDeposit(uint256 assets) external view returns (uint256); + + function previewMint(uint256 shares) external view returns (uint256); + + function previewRedeem(uint256 shares) external view returns (uint256); + + function previewWithdraw(uint256 assets) external view returns (uint256); + + function pricePerShare() external view returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256 assets); + + function rewardsCycleEnd() external view returns (uint32); + + function rewardsCycleLength() external view returns (uint32); + + function symbol() external view returns (string memory); + + function syncRewards() external; + + function totalAssets() external view returns (uint256); + + function totalSupply() external view returns (uint256); + + function transfer(address to, uint256 amount) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256 shares); +} diff --git a/contracts/contracts/interfaces/IWETH9.sol b/contracts/contracts/interfaces/IWETH9.sol new file mode 100644 index 0000000000..c0ff633160 --- /dev/null +++ b/contracts/contracts/interfaces/IWETH9.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IWETH9 { + event Approval(address indexed src, address indexed guy, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + function allowance(address, address) external view returns (uint256); + + function approve(address guy, uint256 wad) external returns (bool); + + function balanceOf(address) external view returns (uint256); + + function decimals() external view returns (uint8); + + function deposit() external payable; + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function totalSupply() external view returns (uint256); + + function transfer(address dst, uint256 wad) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 wad + ) external returns (bool); + + function withdraw(uint256 wad) external; +} diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/vault/OETHZapper.sol new file mode 100644 index 0000000000..40d163a475 --- /dev/null +++ b/contracts/contracts/vault/OETHZapper.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IOUSD } from "../interfaces/IOUSD.sol"; +import { IVault } from "../interfaces/IVault.sol"; +import { IWETH9 } from "../interfaces/IWETH9.sol"; +import { ISfrxETH } from "../interfaces/ISfrxETH.sol"; + +contract OETHZapper { + IOUSD immutable oeth; + IVault immutable vault; + IWETH9 constant weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + ISfrxETH constant sfrxeth = + ISfrxETH(0xac3E018457B222d93114458476f3E3416Abbe38F); + address constant ETH_MARKER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address constant FRXETH = 0x5E8422345238F34275888049021821E8E08CAa1f; + + event MintFrom( + address indexed minter, + address indexed asset, + uint256 amount + ); + + constructor(address _oeth, address _vault) { + oeth = IOUSD(_oeth); + vault = IVault(_vault); + + weth.approve(address(_vault), type(uint256).max); + sfrxeth.approve(address(_vault), type(uint256).max); + IOUSD(_oeth).rebaseOptIn(); // Gas savings for every zap + } + + function zapEthToOETH(uint256 minOETH) external payable returns (uint256) { + weth.deposit{ value: msg.value }(); + emit MintFrom(msg.sender, ETH_MARKER, msg.value); + return _mint(address(weth), minOETH); + } + + function zapSFRXETHToOETH(uint256 amount, uint256 minOETH) + external + returns (uint256) + { + sfrxeth.redeem(amount, address(this), msg.sender); + emit MintFrom(msg.sender, address(sfrxeth), amount); + return _mint(FRXETH, minOETH); + } + + function _mint(address asset, uint256 minOETH) internal returns (uint256) { + uint256 toMint = IERC20(asset).balanceOf(address(this)); + vault.mint(asset, toMint, minOETH); + uint256 mintedAmount = oeth.balanceOf(address(this)); + require(mintedAmount >= minOETH, "Zapper: not enough minted"); + oeth.transfer(msg.sender, mintedAmount); + return mintedAmount; + } +} From 4b0154ed1579c78843ec5eaa0eac3b0bec67d8b9 Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Tue, 11 Apr 2023 17:39:13 -0400 Subject: [PATCH 2/6] Initial draft zapper --- contracts/contracts/vault/OETHZapper.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/vault/OETHZapper.sol index 40d163a475..6d0c62839a 100644 --- a/contracts/contracts/vault/OETHZapper.sol +++ b/contracts/contracts/vault/OETHZapper.sol @@ -31,13 +31,17 @@ contract OETHZapper { IOUSD(_oeth).rebaseOptIn(); // Gas savings for every zap } - function zapEthToOETH(uint256 minOETH) external payable returns (uint256) { + receive() external payable { + deposit(); + } + + function deposit() public payable returns (uint256) { weth.deposit{ value: msg.value }(); emit MintFrom(msg.sender, ETH_MARKER, msg.value); - return _mint(address(weth), minOETH); + return _mint(address(weth), msg.value); } - function zapSFRXETHToOETH(uint256 amount, uint256 minOETH) + function depositSFRXETH(uint256 amount, uint256 minOETH) external returns (uint256) { @@ -51,6 +55,7 @@ contract OETHZapper { vault.mint(asset, toMint, minOETH); uint256 mintedAmount = oeth.balanceOf(address(this)); require(mintedAmount >= minOETH, "Zapper: not enough minted"); + // slither-disable-next-line unchecked-transfer oeth.transfer(msg.sender, mintedAmount); return mintedAmount; } From 0374f5e49a1d2d6ce0d0f1f5f36d63bbdcecc934 Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Wed, 12 Apr 2023 09:44:31 -0400 Subject: [PATCH 3/6] Add deploy file for zapper --- brownie/abi/oethzapper.json | 91 ++++++++++++++++++++++++ contracts/contracts/vault/OETHZapper.sol | 7 +- contracts/deploy/051_oeth.js | 33 ++++++++- contracts/utils/addresses.js | 1 + 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 brownie/abi/oethzapper.json diff --git a/brownie/abi/oethzapper.json b/brownie/abi/oethzapper.json new file mode 100644 index 0000000000..648620339e --- /dev/null +++ b/brownie/abi/oethzapper.json @@ -0,0 +1,91 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_oeth", + "type": "address" + }, + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MintFrom", + "type": "event" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minOETH", + "type": "uint256" + } + ], + "name": "depositSFRXETH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rebaseOptIn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/vault/OETHZapper.sol index 6d0c62839a..c804affe17 100644 --- a/contracts/contracts/vault/OETHZapper.sol +++ b/contracts/contracts/vault/OETHZapper.sol @@ -27,8 +27,7 @@ contract OETHZapper { vault = IVault(_vault); weth.approve(address(_vault), type(uint256).max); - sfrxeth.approve(address(_vault), type(uint256).max); - IOUSD(_oeth).rebaseOptIn(); // Gas savings for every zap + IERC20(FRXETH).approve(address(_vault), type(uint256).max); } receive() external payable { @@ -50,6 +49,10 @@ contract OETHZapper { return _mint(FRXETH, minOETH); } + function rebaseOptIn() external { + oeth.rebaseOptIn(); // Gas savings for every zap + } + function _mint(address asset, uint256 minOETH) internal returns (uint256) { uint256 toMint = IERC20(asset).balanceOf(address(this)); vault.mint(asset, toMint, minOETH); diff --git a/contracts/deploy/051_oeth.js b/contracts/deploy/051_oeth.js index 25d2e20e3c..a3de59a95a 100644 --- a/contracts/deploy/051_oeth.js +++ b/contracts/deploy/051_oeth.js @@ -26,7 +26,13 @@ module.exports = deploymentWithGuardianGovernor( ethers, }); - await deployDripper({ deployWithConfirmation, withConfirmation, ethers }); + // await deployDripper({ deployWithConfirmation, withConfirmation, ethers }); + + await deployZapper({ + deployWithConfirmation, + withConfirmation, + ethers, + }); actions = actions.concat( await deployFraxETHStrategy({ @@ -145,6 +151,11 @@ const deployCore = async ({ cVault.connect(sDeployer).supportAsset(addresses.mainnet.frxETH, 0) ); + // await withConfirmation( + // // 0 stands for DECIMAL unit conversion + // cVault.connect(sDeployer).supportAsset(addresses.mainnet.WETH, 0) + // ); + console.log("Initialized OETHVaultAdmin implementation"); await withConfirmation( @@ -214,6 +225,26 @@ const deployDripper = async ({ ); }; +const deployZapper = async ({ + deployWithConfirmation, + withConfirmation, + ethers, +}) => { + const { deployerAddr } = await getNamedAccounts(); + const sDeployer = await ethers.provider.getSigner(deployerAddr); + + const cOETHProxy = await ethers.getContract("OETHProxy"); + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + + await deployWithConfirmation("OETHZapper", [ + cOETHProxy.address, + cVaultProxy.address, + ]); + + // const cOETHZapper = await ethers.getContract("OETHZapper"); + // await withConfirmation(cOETHZapper.connect(sDeployer).rebaseOptIn()); +}; + /** * Deploy Frax ETH Strategy */ diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 34e1620c0d..cb89492e57 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -149,5 +149,6 @@ addresses.mainnet.WOETHProxy = "0xDcEe70654261AF21C44c093C300eD3Bb97b78192"; // Tokens addresses.mainnet.sfrxETH = "0xac3E018457B222d93114458476f3E3416Abbe38F"; addresses.mainnet.frxETH = "0x5e8422345238f34275888049021821e8e08caa1f"; +addresses.mainnet.WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; module.exports = addresses; From ef4a028e407523c5f242ba537e8deae242b9063f Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Wed, 12 Apr 2023 09:46:23 -0400 Subject: [PATCH 4/6] Remove WETH partial support, out of scope in this PR --- contracts/deploy/051_oeth.js | 3 --- contracts/utils/addresses.js | 1 - 2 files changed, 4 deletions(-) diff --git a/contracts/deploy/051_oeth.js b/contracts/deploy/051_oeth.js index a3de59a95a..bb400526d6 100644 --- a/contracts/deploy/051_oeth.js +++ b/contracts/deploy/051_oeth.js @@ -240,9 +240,6 @@ const deployZapper = async ({ cOETHProxy.address, cVaultProxy.address, ]); - - // const cOETHZapper = await ethers.getContract("OETHZapper"); - // await withConfirmation(cOETHZapper.connect(sDeployer).rebaseOptIn()); }; /** diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index cb89492e57..34e1620c0d 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -149,6 +149,5 @@ addresses.mainnet.WOETHProxy = "0xDcEe70654261AF21C44c093C300eD3Bb97b78192"; // Tokens addresses.mainnet.sfrxETH = "0xac3E018457B222d93114458476f3E3416Abbe38F"; addresses.mainnet.frxETH = "0x5e8422345238f34275888049021821e8e08caa1f"; -addresses.mainnet.WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; module.exports = addresses; From 047dadb395540bc08509ac426e420795cddb9823 Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Wed, 12 Apr 2023 12:33:42 -0400 Subject: [PATCH 5/6] Happy Slither --- contracts/contracts/vault/OETHZapper.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/vault/OETHZapper.sol index c804affe17..f75e4eda9e 100644 --- a/contracts/contracts/vault/OETHZapper.sol +++ b/contracts/contracts/vault/OETHZapper.sol @@ -26,7 +26,9 @@ contract OETHZapper { oeth = IOUSD(_oeth); vault = IVault(_vault); + // slither-disable-next-line unused-return weth.approve(address(_vault), type(uint256).max); + // slither-disable-next-line unused-return IERC20(FRXETH).approve(address(_vault), type(uint256).max); } @@ -44,6 +46,7 @@ contract OETHZapper { external returns (uint256) { + // slither-disable-next-line unused-return sfrxeth.redeem(amount, address(this), msg.sender); emit MintFrom(msg.sender, address(sfrxeth), amount); return _mint(FRXETH, minOETH); From 9bda0c304d31673ccd13eed62f39df041dfadedd Mon Sep 17 00:00:00 2001 From: Daniel Von Fange Date: Wed, 12 Apr 2023 13:53:28 -0400 Subject: [PATCH 6/6] WETH support --- contracts/deploy/051_oeth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/deploy/051_oeth.js b/contracts/deploy/051_oeth.js index f3955b2e28..1176579576 100644 --- a/contracts/deploy/051_oeth.js +++ b/contracts/deploy/051_oeth.js @@ -151,10 +151,10 @@ const deployCore = async ({ cVault.connect(sDeployer).supportAsset(addresses.mainnet.frxETH, 0) ); - // await withConfirmation( - // // 0 stands for DECIMAL unit conversion - // cVault.connect(sDeployer).supportAsset(addresses.mainnet.WETH, 0) - // ); + await withConfirmation( + // 0 stands for DECIMAL unit conversion + cVault.connect(sDeployer).supportAsset(addresses.mainnet.WETH, 0) + ); console.log("Initialized OETHVaultAdmin implementation");