From 3aa9678e3caebd710f0660c985bdfd56b2f974de Mon Sep 17 00:00:00 2001 From: Jacob Eliosoff Date: Mon, 5 Oct 2020 05:00:12 -0400 Subject: [PATCH] Try unifying Proxy.mint() and Proxy.mintWithEth() into just Proxy.mint(). (Etc) --- contracts/Proxy.sol | 117 +++++++++++++---------------------- test/04_Proxy_Eth.test.js | 21 ++++--- test/05_Proxy_Limits.test.js | 21 ++++--- 3 files changed, 65 insertions(+), 94 deletions(-) diff --git a/contracts/Proxy.sol b/contracts/Proxy.sol index 7405d49..a00a78d 100644 --- a/contracts/Proxy.sol +++ b/contracts/Proxy.sol @@ -7,6 +7,8 @@ import "./external/IWETH9.sol"; contract Proxy { + enum EthType {ETH, WETH} + using Address for address payable; IUSM public usm; IWETH9 public weth; @@ -20,108 +22,75 @@ contract Proxy { weth.approve(address(usm), uint(-1)); } - // ------------------- - // Using Ether - // ------------------- - /// @dev The WETH9 contract will send ether to Proxy on `weth.withdraw` using this function. receive() external payable { } - /// @dev Users use `mint` in Proxy to post ETH to USM (amount = msg.value), which will be converted to Weth here. + /// @dev Users use `mint()` in Proxy to input either WETH or ETH: either one results in passing WETH to `USM.mint()`. + /// @param ethIn Amount of WETH/ETH to use for minting USM. /// @param minUsmOut Minimum accepted USM for a successful mint. - function mintWithEth(uint minUsmOut) + /// @param inputType Whether the user passes in WETH, or ETH (which is immediately converted to WETH). + function mint(uint ethIn, uint minUsmOut, EthType inputType) external payable returns (uint) { - weth.deposit{ value: msg.value }(); - uint usmOut = usm.mint(address(this), msg.sender, msg.value); + address payer = (inputType == EthType.ETH ? address(this) : msg.sender); + if (inputType == EthType.ETH) { + require(msg.value == ethIn, "ETH input misspecified"); + weth.deposit{ value: msg.value }(); + } + uint usmOut = usm.mint(payer, msg.sender, ethIn); require(usmOut >= minUsmOut, "Limit not reached"); return usmOut; } - /// @dev Users wishing to withdraw their Weth as ETH from USM should use this function. + /// @dev Users wishing to withdraw their WETH from USM, either as WETH or as ETH, should use this function. /// Users must have called `controller.addDelegate(Proxy.address)` to authorize Proxy to act in their behalf. /// @param usmToBurn Amount of USM to burn. - /// @param minEthOut Minimum accepted ETH for a successful burn. - function burnForEth(uint usmToBurn, uint minEthOut) + /// @param minEthOut Minimum accepted WETH/ETH for a successful burn. + /// @param outputType Whether to send the user WETH, or first convert it to ETH. + function burn(uint usmToBurn, uint minEthOut, EthType outputType) external returns (uint) { - uint ethOut = usm.burn(msg.sender, address(this), usmToBurn); + address receiver = (outputType == EthType.ETH ? address(this) : msg.sender); + uint ethOut = usm.burn(msg.sender, receiver, usmToBurn); require(ethOut >= minEthOut, "Limit not reached"); - weth.withdraw(ethOut); - msg.sender.sendValue(ethOut); + if (outputType == EthType.ETH) { + weth.withdraw(ethOut); + msg.sender.sendValue(ethOut); + } return ethOut; } - /// @notice Funds the pool with ETH, converted to WETH - /// @param minFumOut Minimum accepted FUM for a successful burn. - function fundWithEth(uint minFumOut) + /// @notice Funds the pool either with WETH, or with ETH (then converted to WETH) + /// @param ethIn Amount of WETH/ETH to use for minting FUM. + /// @param minFumOut Minimum accepted FUM for a successful fund. + /// @param inputType Whether the user passes in WETH, or ETH (which is immediately converted to WETH). + function fund(uint ethIn, uint minFumOut, EthType inputType) external payable returns (uint) { - weth.deposit{ value: msg.value }(); - uint fumOut = usm.fund(address(this), msg.sender, msg.value); - require(fumOut >= minFumOut, "Limit not reached"); - return fumOut; - } - - /// @notice Defunds the pool by sending FUM out in exchange for equivalent ETH from the pool - /// @param fumToBurn Amount of FUM to burn. - /// @param minEthOut Minimum accepted ETH for a successful defund. - function defundForEth(uint fumToBurn, uint minEthOut) - external returns (uint) - { - uint ethOut = usm.defund(msg.sender, address(this), fumToBurn); - require(ethOut >= minEthOut, "Limit not reached"); - weth.withdraw(ethOut); - msg.sender.sendValue(ethOut); - return ethOut; - } - - // ------------------- - // Using Wrapped Ether - // ------------------- - - /// @dev Users use `mint` in Proxy to post Weth to USM. - /// @param ethIn Amount of wrapped eth to use for minting USM. - /// @param minUsmOut Minimum accepted USM for a successful mint. - function mint(uint ethIn, uint minUsmOut) - external returns (uint) - { - uint usmOut = usm.mint(msg.sender, msg.sender, ethIn); - require(usmOut >= minUsmOut, "Limit not reached"); - return usmOut; - } - - /// @dev Users wishing to withdraw their Weth from USM should use this function. - /// Users must have called `controller.addDelegate(Proxy.address)` to authorize Proxy to act in their behalf. - /// @param usmToBurn Amount of USM to burn. - /// @param minEthOut Minimum accepted WETH for a successful burn. - function burn(uint usmToBurn, uint minEthOut) - external returns (uint) - { - uint ethOut = usm.burn(msg.sender, msg.sender, usmToBurn); - require(ethOut >= minEthOut, "Limit not reached"); - return ethOut; - } - - /// @notice Funds the pool with WETH - /// @param ethIn Amount of wrapped eth to use for minting FUM. - /// @param minFumOut Minimum accepted FUM for a successful burn. - function fund(uint ethIn, uint minFumOut) - external returns (uint) - { - uint fumOut = usm.fund(msg.sender, msg.sender, ethIn); + address payer = (inputType == EthType.ETH ? address(this) : msg.sender); + if (inputType == EthType.ETH) { + require(msg.value == ethIn, "ETH input misspecified"); + weth.deposit{ value: msg.value }(); + } + uint fumOut = usm.fund(payer, msg.sender, ethIn); require(fumOut >= minFumOut, "Limit not reached"); return fumOut; } - /// @notice Defunds the pool by sending FUM out in exchange for equivalent WETH from the pool + /// @notice Defunds the pool by redeeming FUM in exchange for equivalent WETH from the pool (optionally then converted to ETH) /// @param fumToBurn Amount of FUM to burn. - /// @param minEthOut Minimum accepted WETH for a successful defund. - function defund(uint fumToBurn, uint minEthOut) + /// @param minEthOut Minimum accepted WETH/ETH for a successful defund. + /// @param outputType Whether to send the user WETH, or first convert it to ETH. + function defund(uint fumToBurn, uint minEthOut, EthType outputType) external returns (uint) { - uint ethOut = usm.defund(msg.sender, msg.sender, fumToBurn); + address receiver = (outputType == EthType.ETH ? address(this) : msg.sender); + uint ethOut = usm.defund(msg.sender, receiver, fumToBurn); require(ethOut >= minEthOut, "Limit not reached"); + if (outputType == EthType.ETH) { + weth.withdraw(ethOut); + msg.sender.sendValue(ethOut); + } return ethOut; } } diff --git a/test/04_Proxy_Eth.test.js b/test/04_Proxy_Eth.test.js index 8faee43..0caae9f 100644 --- a/test/04_Proxy_Eth.test.js +++ b/test/04_Proxy_Eth.test.js @@ -29,6 +29,7 @@ contract('USM - Proxy - Eth', (accounts) => { const WAD = new BN('1000000000000000000') const sides = { BUY: 0, SELL: 1 } + const ethTypes = { ETH: 0, WETH: 1 } const price = new BN('25000000000') const shift = EIGHT const MAX = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' @@ -58,25 +59,25 @@ contract('USM - Proxy - Eth', (accounts) => { const fumSellPrice = (await usm.fumPrice(sides.SELL)) fumBuyPrice.toString().should.equal(fumSellPrice.toString()) - await proxy.fundWithEth(0, { from: user1, value: oneEth }) + await proxy.fund(oneEth, 0, ethTypes.ETH, { from: user1, value: oneEth }) const ethPool2 = (await weth.balanceOf(usm.address)) ethPool2.toString().should.equal(oneEth.toString()) }) it('does not mint FUM if minimum not reached', async () => { await expectRevert( - proxy.fundWithEth(MAX, { from: user1, value: oneEth }), + proxy.fund(oneEth, MAX, ethTypes.ETH, { from: user1, value: oneEth }), "Limit not reached", ) }) describe('with existing FUM supply', () => { beforeEach(async () => { - await proxy.fundWithEth(0, { from: user1, value: oneEth }) + await proxy.fund(oneEth, 0, ethTypes.ETH, { from: user1, value: oneEth }) }) it('allows minting USM', async () => { - await proxy.mintWithEth(0, { from: user1, value: oneEth }) + await proxy.mint(oneEth, 0, ethTypes.ETH, { from: user1, value: oneEth }) const ethPool2 = (await weth.balanceOf(usm.address)) ethPool2.toString().should.equal(oneEth.mul(TWO).toString()) @@ -86,14 +87,14 @@ contract('USM - Proxy - Eth', (accounts) => { it('does not mint USM if minimum not reached', async () => { await expectRevert( - proxy.mintWithEth(MAX, { from: user1, value: oneEth }), + proxy.mint(oneEth, MAX, ethTypes.ETH, { from: user1, value: oneEth }), "Limit not reached", ) }) describe('with existing USM supply', () => { beforeEach(async () => { - await proxy.mintWithEth(0, { from: user1, value: oneEth }) + await proxy.mint(oneEth, 0, ethTypes.ETH, { from: user1, value: oneEth }) }) it('allows burning FUM', async () => { @@ -106,7 +107,7 @@ contract('USM - Proxy - Eth', (accounts) => { fumBalance.toString().should.equal(targetFumBalance.toString()) const fumToBurn = priceWAD.div(TWO) - await proxy.defundForEth(fumToBurn, 0, { from: user1, gasPrice: 0 }) // Don't use eth on gas + await proxy.defund(fumToBurn, 0, ethTypes.ETH, { from: user1, gasPrice: 0 }) // Don't use eth on gas const fumBalance2 = (await fum.balanceOf(user1)) fumBalance2.toString().should.equal(fumBalance.sub(fumToBurn).toString()) @@ -121,7 +122,7 @@ contract('USM - Proxy - Eth', (accounts) => { await expectRevert( // Defunding the full balance would fail (violate MAX_DEBT_RATIO), so just defund half: - proxy.defundForEth(fumBalance.div(TWO), MAX, { from: user1 }), + proxy.defund(fumBalance.div(TWO), MAX, ethTypes.ETH, { from: user1 }), "Limit not reached", ) }) @@ -133,7 +134,7 @@ contract('USM - Proxy - Eth', (accounts) => { const ethBalance = new BN(await web3.eth.getBalance(user1)) const usmToBurn = (await usm.balanceOf(user1)) - await proxy.burnForEth(usmToBurn, 0, { from: user1, gasPrice: 0}) + await proxy.burn(usmToBurn, 0, ethTypes.ETH, { from: user1, gasPrice: 0}) const userUsmBalance2 = (await usm.balanceOf(user1)) userUsmBalance2.toString().should.equal('0') @@ -147,7 +148,7 @@ contract('USM - Proxy - Eth', (accounts) => { const usmBalance = (await usm.balanceOf(user1)) await expectRevert( - proxy.burnForEth(usmBalance, MAX, { from: user1 }), + proxy.burn(usmBalance, MAX, ethTypes.ETH, { from: user1 }), "Limit not reached", ) }) diff --git a/test/05_Proxy_Limits.test.js b/test/05_Proxy_Limits.test.js index 4c12950..6a72703 100644 --- a/test/05_Proxy_Limits.test.js +++ b/test/05_Proxy_Limits.test.js @@ -19,6 +19,7 @@ contract('USM - Proxy - Limits', (accounts) => { let [deployer, user1, user2] = accounts const sides = { BUY: 0, SELL: 1 } + const ethTypes = { ETH: 0, WETH: 1 } const price = new BN('25000000000') const shift = new BN('8') const MAX = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' @@ -52,7 +53,7 @@ contract('USM - Proxy - Limits', (accounts) => { it('allows minting FUM', async () => { - await proxy.fund(oneEth, 0, { from: user1 }) + await proxy.fund(oneEth, 0, ethTypes.WETH, { from: user1 }) const newEthPool = (await weth.balanceOf(usm.address)) newEthPool.toString().should.equal(oneEth.toString()) @@ -60,39 +61,39 @@ contract('USM - Proxy - Limits', (accounts) => { it('does not mint FUM if minimum not reached', async () => { await expectRevert( - proxy.fund(oneEth, MAX, { from: user1 }), + proxy.fund(oneEth, MAX, ethTypes.WETH, { from: user1 }), "Limit not reached", ) }) describe('with existing FUM supply', () => { beforeEach(async () => { - await proxy.fund(oneEth, 0, { from: user1 }) + await proxy.fund(oneEth, 0, ethTypes.WETH, { from: user1 }) }) it('allows minting USM', async () => { - await proxy.mint(oneEth, 0, { from: user1 }) + await proxy.mint(oneEth, 0, ethTypes.WETH, { from: user1 }) const usmBalance = (await usm.balanceOf(user1)) usmBalance.toString().should.equal(oneEth.mul(priceWAD).div(WAD).div(new BN('2')).toString()) }) it('does not mint USM if minimum not reached', async () => { await expectRevert( - proxy.mint(oneEth, MAX, { from: user1 }), + proxy.mint(oneEth, MAX, ethTypes.WETH, { from: user1 }), "Limit not reached", ) }) describe('with existing USM supply', () => { beforeEach(async () => { - await proxy.mint(oneEth, 0, { from: user1 }) + await proxy.mint(oneEth, 0, ethTypes.WETH, { from: user1 }) }) it('allows burning FUM', async () => { const targetFumBalance = oneEth.mul(priceWAD).div(WAD) // see "allows minting FUM" above const startingBalance = await weth.balanceOf(user1) - await proxy.defund(priceWAD.mul(new BN('3')).div(new BN('4')), 0, { from: user1 }) // defund 75% of our fum + await proxy.defund(priceWAD.mul(new BN('3')).div(new BN('4')), 0, ethTypes.WETH, { from: user1 }) // defund 75% of our fum const newFumBalance = (await fum.balanceOf(user1)) newFumBalance.toString().should.equal(targetFumBalance.div(new BN('4')).toString()) // should be 25% of what it was @@ -101,7 +102,7 @@ contract('USM - Proxy - Limits', (accounts) => { it('does not burn FUM if minimum not reached', async () => { await expectRevert( - proxy.defund(priceWAD.mul(new BN('3')).div(new BN('4')), MAX, { from: user1 }), + proxy.defund(priceWAD.mul(new BN('3')).div(new BN('4')), MAX, ethTypes.WETH, { from: user1 }), "Limit not reached", ) }) @@ -110,7 +111,7 @@ contract('USM - Proxy - Limits', (accounts) => { const usmBalance = (await usm.balanceOf(user1)).toString() const startingBalance = await weth.balanceOf(user1) - await proxy.burn(usmBalance, 0, { from: user1 }) + await proxy.burn(usmBalance, 0, ethTypes.WETH, { from: user1 }) const newUsmBalance = (await usm.balanceOf(user1)) newUsmBalance.toString().should.equal('0') @@ -121,7 +122,7 @@ contract('USM - Proxy - Limits', (accounts) => { const usmBalance = (await usm.balanceOf(user1)).toString() await expectRevert( - proxy.burn(usmBalance, MAX, { from: user1 }), + proxy.burn(usmBalance, MAX, ethTypes.WETH, { from: user1 }), "Limit not reached", ) })