Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try unifying Proxy.mint() and Proxy.mintWithEth() into just Proxy.mint(). (Etc) #44

Merged
merged 1 commit into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 43 additions & 74 deletions contracts/Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
21 changes: 11 additions & 10 deletions test/04_Proxy_Eth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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())

Expand All @@ -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 () => {
Expand All @@ -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())

Expand All @@ -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",
)
})
Expand All @@ -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')

Expand All @@ -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",
)
})
Expand Down
21 changes: 11 additions & 10 deletions test/05_Proxy_Limits.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -52,47 +53,47 @@ 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())
})

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

Expand All @@ -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",
)
})
Expand All @@ -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')

Expand All @@ -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",
)
})
Expand Down