From 2705ab4ad9f858f1cad1c3aa677705822f2f0efb Mon Sep 17 00:00:00 2001 From: eccheung4 Date: Thu, 9 Sep 2021 16:32:24 -0700 Subject: [PATCH] Fix: Add convertEthToWeth logic for WETH PrizePools --- .../connectors/pooltogether/events.sol | 2 +- .../connectors/pooltogether/interface.sol | 2 +- .../mainnet/connectors/pooltogether/main.sol | 40 ++++- test/pooltogether/pooltogether.test.js | 159 +++++++++++++++++- 4 files changed, 192 insertions(+), 11 deletions(-) diff --git a/contracts/mainnet/connectors/pooltogether/events.sol b/contracts/mainnet/connectors/pooltogether/events.sol index 12e4af63..46415d0d 100644 --- a/contracts/mainnet/connectors/pooltogether/events.sol +++ b/contracts/mainnet/connectors/pooltogether/events.sol @@ -4,7 +4,7 @@ import { TokenFaucetInterface } from "./interface.sol"; contract Events { event LogDepositTo(address prizePool, address to, uint256 amount, address controlledToken, uint256 getId, uint256 setId); - event LogWithdrawInstantlyFrom(address prizePool, address from, uint256 amount, address controlledToken, uint256 maximumExitFee, uint256 getId, uint256 setId); + event LogWithdrawInstantlyFrom(address prizePool, address from, uint256 amount, address controlledToken, uint256 maximumExitFee, uint256 exitFee, uint256 getId, uint256 setId); event LogClaim(address tokenFaucet, address user, uint256 claimed, uint256 setId); event LogClaimAll(address tokenFaucetProxyFactory, address user, TokenFaucetInterface[] tokenFaucets); event LogDepositToPod(address prizePoolToken, address pod, address to, uint256 amount, uint256 podShare, uint256 getId, uint256 setId); diff --git a/contracts/mainnet/connectors/pooltogether/interface.sol b/contracts/mainnet/connectors/pooltogether/interface.sol index dc32d955..c63c08cc 100644 --- a/contracts/mainnet/connectors/pooltogether/interface.sol +++ b/contracts/mainnet/connectors/pooltogether/interface.sol @@ -4,7 +4,6 @@ interface PrizePoolInterface { function token() external view returns (address); function depositTo( address to, uint256 amount, address controlledToken, address referrer) external; function withdrawInstantlyFrom( address from, uint256 amount, address controlledToken, uint256 maximumExitFee) external returns (uint256); - function calculateEarlyExitFee( address from, address controlledToken, uint256 amount) external returns ( uint256 exitFee, uint256 burnedCredit); } interface TokenFaucetInterface { @@ -16,6 +15,7 @@ interface TokenFaucetProxyFactoryInterface { } interface PodInterface { + function token() external view returns (address); function depositTo(address to, uint256 tokenAmount) external returns (uint256); function withdraw(uint256 shareAmount, uint256 maxFee) external returns (uint256); function balanceOf(address account) external view returns (uint256); diff --git a/contracts/mainnet/connectors/pooltogether/main.sol b/contracts/mainnet/connectors/pooltogether/main.sol index 5218cfa9..09d23506 100644 --- a/contracts/mainnet/connectors/pooltogether/main.sol +++ b/contracts/mainnet/connectors/pooltogether/main.sol @@ -10,6 +10,7 @@ pragma solidity ^0.7.0; import { PrizePoolInterface, TokenFaucetInterface, TokenFaucetProxyFactoryInterface, PodInterface } from "./interface.sol"; import { TokenInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; import { Events } from "./events.sol"; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; @@ -39,10 +40,18 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { PrizePoolInterface prizePoolContract = PrizePoolInterface(prizePool); address prizePoolToken = prizePoolContract.token(); - // Approve prizePool + bool isEth = prizePoolToken == wethAddr; TokenInterface tokenContract = TokenInterface(prizePoolToken); - _amount = _amount == uint256(-1) ? tokenContract.balanceOf(address(this)) : _amount; - tokenContract.approve(prizePool, _amount); + + if (isEth) { + _amount = _amount == uint256(-1) ? address(this).balance : _amount; + convertEthToWeth(isEth, tokenContract, _amount); + } else { + _amount = _amount == uint256(-1) ? tokenContract.balanceOf(address(this)) : _amount; + } + + // Approve prizePool + approve(tokenContract, prizePool, _amount); prizePoolContract.depositTo(address(this), _amount, controlledToken, address(0)); @@ -74,16 +83,22 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { uint _amount = getUint(getId, amount); PrizePoolInterface prizePoolContract = PrizePoolInterface(prizePool); + address prizePoolToken = prizePoolContract.token(); + TokenInterface tokenContract = TokenInterface(prizePoolToken); TokenInterface ticketToken = TokenInterface(controlledToken); _amount = _amount == uint256(-1) ? ticketToken.balanceOf(address(this)) : _amount; - prizePoolContract.withdrawInstantlyFrom(address(this), _amount, controlledToken, maximumExitFee); + uint exitFee = prizePoolContract.withdrawInstantlyFrom(address(this), _amount, controlledToken, maximumExitFee); + + _amount = _amount - exitFee; + + convertWethToEth(prizePoolToken == wethAddr, tokenContract, _amount); setUint(setId, _amount); - _eventName = "LogWithdrawInstantlyFrom(address,address,uint256,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(address(prizePool), address(this), _amount, address(controlledToken), maximumExitFee, getId, setId); + _eventName = "LogWithdrawInstantlyFrom(address,address,uint256,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(prizePool), address(this), _amount, address(controlledToken), maximumExitFee, exitFee, getId, setId); } /** @@ -144,10 +159,17 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { PodInterface podContract = PodInterface(pod); + bool isEth = prizePoolToken == wethAddr; + // Approve pod TokenInterface tokenContract = TokenInterface(prizePoolToken); - _tokenAmount = _tokenAmount == uint256(-1) ? tokenContract.balanceOf(address(this)) : _tokenAmount; - tokenContract.approve(pod, _tokenAmount); + if (isEth) { + _tokenAmount = _tokenAmount == uint256(-1) ? address(this).balance : _tokenAmount; + convertEthToWeth(isEth, tokenContract, _tokenAmount); + } else { + _tokenAmount = _tokenAmount == uint256(-1) ? tokenContract.balanceOf(address(this)) : _tokenAmount; + } + approve(tokenContract, pod, _tokenAmount); uint256 _podShare = podContract.depositTo(address(this), _tokenAmount); @@ -182,6 +204,8 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { uint256 _tokenAmount = podContract.withdraw(_shareAmount, maxFee); + convertWethToEth(address(podContract.token()) == wethAddr, TokenInterface(podContract.token()), _tokenAmount); + setUint(setId, _tokenAmount); _eventName = "LogWithdrawFromPod(address,uint256,uint256,uint256,uint256,uint256)"; diff --git a/test/pooltogether/pooltogether.test.js b/test/pooltogether/pooltogether.test.js index d50ace47..52084e7f 100644 --- a/test/pooltogether/pooltogether.test.js +++ b/test/pooltogether/pooltogether.test.js @@ -34,12 +34,22 @@ const POOL_PRIZE_POOL_ADDR = "0x396b4489da692788e327e2e4b2b0459a5ef26791" // P const PT_POOL_TICKET_ADDR = "0x27d22a7648e955e510a40bdb058333e9190d12d4" // Pool Together POOL Ticket const WETH_ADDR = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" // WETH +// Community WETH Prize Pool (Rari): https://reference-app.pooltogether.com/pools/mainnet/0xa88ca010b32a54d446fc38091ddbca55750cbfc3/manage#stats +const WETH_PRIZE_POOL_ADDR = "0xa88ca010b32a54d446fc38091ddbca55750cbfc3" // Community WETH Prize Pool (Rari) +const WETH_POOL_TICKET_ADDR = "0x9b5c30aeb9ce2a6a121cea9a85bc0d662f6d9b40" // Community WETH Prize Pool Ticket (Rari) + const prizePoolABI = [ "function calculateEarlyExitFee( address from, address controlledToken, uint256 amount) external returns ( uint256 exitFee, uint256 burnedCredit)" ] const podABI = [ - "function getEarlyExitFee(uint256 amount) external returns (uint256)" + "function getEarlyExitFee(uint256 amount) external returns (uint256)", + "function balanceOfUnderlying(address user) external view returns (uint256 amount)" +] + +const POD_FACTORY_ADDRESS = "0x4e3a9f9fbafb2ec49727cffa2a411f7a0c1c4ce1" +const podFactoryABI = [ + "function create( address _prizePool, address _ticket, address _faucet, address _manager, uint8 _decimals) external returns (address pod)" ] describe("PoolTogether", function () { @@ -603,4 +613,151 @@ describe("PoolTogether", function () { expect(poolPoolTicketBalanceAfter, `PoolTogether POOL Ticket greater than 0`).to.be.gt(0); }); }) + + describe("Main - WETH Prize Pool Test", function () { + it("Deposit 1 ETH into WETH Prize Pool and withdraw immediately", async function () { + const amount = ethers.utils.parseEther("1") // 1 ETH + const setId = "83478237" + const spells = [ + { + connector: ptConnectorName, + method: "depositTo", + args: [WETH_PRIZE_POOL_ADDR, amount, WETH_POOL_TICKET_ADDR, 0, setId] + }, + { + connector: ptConnectorName, + method: "withdrawInstantlyFrom", + args: [WETH_PRIZE_POOL_ADDR, amount, WETH_POOL_TICKET_ADDR, amount, setId, 0] + }, + ] + // Before Spell + const ethBalanceBefore = await ethers.provider.getBalance(dsaWallet0.address); + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After spell + const ethBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + + // ETH used for transaction + expect(ethBalanceAfter, `ETH Balance less than before spell because of early withdrawal fee`).to.be.lte(ethBalanceBefore); + }); + + it("Deposit 1 ETH into WETH Prize Pool, wait 14 days, then withdraw", async function () { + const amount = ethers.utils.parseEther("1") // 1 ETH + const depositSpell = [ + { + connector: ptConnectorName, + method: "depositTo", + args: [WETH_PRIZE_POOL_ADDR, amount, WETH_POOL_TICKET_ADDR, 0, 0] + } + ] + + const withdrawSpell = [ + { + connector: ptConnectorName, + method: "withdrawInstantlyFrom", + args: [WETH_PRIZE_POOL_ADDR, amount, WETH_POOL_TICKET_ADDR, amount, 0, 0] + } + ] + + // Before Deposit Spell + let ethBalanceBefore = await ethers.provider.getBalance(dsaWallet0.address); + + // Run deposit spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(depositSpell), wallet1.address) + const receipt = await tx.wait() + + // After Deposit spell + let ethBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + + expect(ethBalanceAfter, `ETH Balance less than before spell`).to.be.lte(ethBalanceBefore); + + // Increase time by 11 days so we get back all ETH without early withdrawal fee + await ethers.provider.send("evm_increaseTime", [14*24*60*60]); + await ethers.provider.send("evm_mine"); + + // Run withdraw spell transaction + const tx2 = await dsaWallet0.connect(wallet0).cast(...encodeSpells(withdrawSpell), wallet1.address) + const receipt2 = await tx.wait() + + // After Deposit spell + ethBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + + expect(ethBalanceAfter, `ETH Balance equal to before spell because no early exit fee`).to.be.eq(ethBalanceBefore); + }); + }); + + describe("Main - WETH Pod Test", function() { + let podAddress + it("Should deposit 1 ETH in WETH Pod and get Pod Ticket", async function() { + const amount = ethers.utils.parseEther("1") + + // Create Pod for WETH Prize Pool (Rari) + const podFactoryContract = new ethers.Contract(POD_FACTORY_ADDRESS, podFactoryABI, masterSigner) + podAddress = await podFactoryContract.callStatic.create(WETH_PRIZE_POOL_ADDR, WETH_POOL_TICKET_ADDR, constants.address_zero, wallet0.address, 18) + await podFactoryContract.create(WETH_PRIZE_POOL_ADDR, WETH_POOL_TICKET_ADDR, constants.address_zero, wallet0.address, 18) + + const spells = [ + { + connector: ptConnectorName, + method: "depositToPod", + args: [WETH_ADDR, podAddress, amount, 0, 0] + } + ] + + // Before Deposit Spell + const podContract = new ethers.Contract(podAddress, podABI, ethers.provider); + let podBalanceBefore = await podContract.balanceOfUnderlying(dsaWallet0.address) + expect(podBalanceBefore, `Pod balance equal to 0`).to.be.eq(0); + + let ethBalanceBefore = await ethers.provider.getBalance(dsaWallet0.address); + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After Deposit spell + let ethBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + expect(ethBalanceAfter, `ETH balance less than before`).to.be.lt(ethBalanceBefore); + + podBalanceAfter = await podContract.balanceOfUnderlying(dsaWallet0.address) + expect(podBalanceAfter, `Pod balance equal to 1`).to.be.eq(ethers.utils.parseEther("1")); + }); + + it("Should withdraw 1 Ticket from WETH Pod and get back ETH", async function() { + const amount = ethers.utils.parseEther("1") + + const podContract = new ethers.Contract(podAddress, podABI, ethers.provider); + let maxFee = await podContract.callStatic["getEarlyExitFee"](amount); + expect(maxFee, "Exit Fee equal to 0 DAI because token still in float").to.be.eq(0); + // maxFee depends on if token has been deposited to PrizePool yet + + const spells = [ + { + connector: ptConnectorName, + method: "withdrawFromPod", + args: [podAddress, amount, maxFee, 0, 0] + } + ] + + // Before Deposit Spell + let podBalanceBefore = await podContract.balanceOfUnderlying(dsaWallet0.address) + expect(podBalanceBefore, `Pod balance equal to 1`).to.be.eq(ethers.utils.parseEther("1")); + + let ethBalanceBefore = await ethers.provider.getBalance(dsaWallet0.address); + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After Deposit spell + let ethBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + expect(ethBalanceAfter, `ETH balance greater than before`).to.be.gt(ethBalanceBefore); + + podBalanceAfter = await podContract.balanceOfUnderlying(dsaWallet0.address) + expect(podBalanceAfter, `Pod balance equal to 0`).to.be.eq(ethers.utils.parseEther("0")); + }); + }); }) \ No newline at end of file