Skip to content

Commit

Permalink
Merge pull request #44 from usmfum/feat/unify-proxy-api
Browse files Browse the repository at this point in the history
Try unifying Proxy.mint() and Proxy.mintWithEth() into just Proxy.mint().  (Etc)
  • Loading branch information
alcueca authored Oct 6, 2020
2 parents 1e2a85e + 3aa9678 commit dce3395
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 94 deletions.
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

0 comments on commit dce3395

Please sign in to comment.