diff --git a/contracts/mainnet/connectors/pooltogether/events.sol b/contracts/mainnet/connectors/pooltogether/events.sol index 459f7431..e1a844d8 100644 --- a/contracts/mainnet/connectors/pooltogether/events.sol +++ b/contracts/mainnet/connectors/pooltogether/events.sol @@ -7,4 +7,6 @@ contract Events { event LogWithdrawInstantlyFrom(address prizePool, address from, uint256 amount, address controlledToken, uint256 maximumExitFee, uint256 getId, uint256 setId); event LogClaim(address tokenFaucet, address user); event LogClaimAll(address tokenFaucetProxyFactory, address user, TokenFaucetInterface[] tokenFaucets); + event LogDepositToPod(address prizePool, address pod, address to, uint256 amount, uint256 getId, uint256 setId); + event LogWithdrawFromPod(address pod, uint256 shareAmount, uint256 maxFee, uint256 getId, uint256 setId); } \ No newline at end of file diff --git a/contracts/mainnet/connectors/pooltogether/interface.sol b/contracts/mainnet/connectors/pooltogether/interface.sol index c82d1b8b..6ac71665 100644 --- a/contracts/mainnet/connectors/pooltogether/interface.sol +++ b/contracts/mainnet/connectors/pooltogether/interface.sol @@ -12,4 +12,9 @@ interface TokenFaucetInterface { interface TokenFaucetProxyFactoryInterface { function claimAll(address user, TokenFaucetInterface[] calldata tokenFaucets) external; +} + +interface PodInterface { + function depositTo(address to, uint256 tokenAmount) external returns (uint256); + function withdraw(uint256 shareAmount, uint256 maxFee) external returns (uint256); } \ No newline at end of file diff --git a/contracts/mainnet/connectors/pooltogether/main.sol b/contracts/mainnet/connectors/pooltogether/main.sol index 6684d2bf..85c52185 100644 --- a/contracts/mainnet/connectors/pooltogether/main.sol +++ b/contracts/mainnet/connectors/pooltogether/main.sol @@ -7,7 +7,7 @@ pragma solidity ^0.7.0; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - import { PrizePoolInterface, TokenFaucetInterface, TokenFaucetProxyFactoryInterface } from "./interface.sol"; + import { PrizePoolInterface, TokenFaucetInterface, TokenFaucetProxyFactoryInterface, PodInterface } from "./interface.sol"; import { TokenInterface } from "../../common/interfaces.sol"; import { Events } from "./events.sol"; @@ -37,7 +37,7 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { address referrer, uint256 getId, uint256 setId - ) external returns ( string memory _eventName, bytes memory _eventParam) { + ) external payable returns ( string memory _eventName, bytes memory _eventParam) { uint _amount = getUint(getId, amount); PrizePoolInterface prizePoolContract = PrizePoolInterface(prizePool); @@ -75,7 +75,7 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { uint256 maximumExitFee, uint256 getId, uint256 setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amount = getUint(getId, amount); PrizePoolInterface prizePoolContract = PrizePoolInterface(prizePool); @@ -97,7 +97,7 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { function claim ( address tokenFaucet, address user - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { TokenFaucetInterface tokenFaucetContract = TokenFaucetInterface(tokenFaucet); tokenFaucetContract.claim(user); @@ -117,7 +117,7 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { address tokenFaucetProxyFactory, address user, TokenFaucetInterface[] calldata tokenFaucets - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { TokenFaucetProxyFactoryInterface tokenFaucetProxyFactoryContract = TokenFaucetProxyFactoryInterface(tokenFaucetProxyFactory); tokenFaucetProxyFactoryContract.claimAll(user, tokenFaucets); @@ -125,6 +125,73 @@ abstract contract PoolTogetherResolver is Events, DSMath, Basic { _eventName = "LogClaimAll(address,address,TokenFaucetInterface[])"; _eventParam = abi.encode(address(tokenFaucetProxyFactory), address(user), tokenFaucets); } + + /** + * @dev Deposit into Pod + * @notice Deposit assets into the Pod in exchange for share tokens + * @param prizePool PrizePool address to deposit to + * @param pod Pod address to deposit to + * @param to The address that shall receive the Pod shares + * @param tokenAmount The amount of tokens to deposit. These are the same tokens used to deposit into the underlying prize pool. + * @param getId Get token amount at this ID from `InstaMemory` Contract. + * @param setId Set token amount at this ID in `InstaMemory` Contract. + */ + function depositToPod( + address prizePool, + address pod, + address to, + uint256 tokenAmount, + uint256 getId, + uint256 setId + ) external payable returns ( string memory _eventName, bytes memory _eventParam) { + uint _tokenAmount= getUint(getId, tokenAmount); + + PrizePoolInterface prizePoolContract = PrizePoolInterface(prizePool); + address prizePoolToken = prizePoolContract.token(); + + PodInterface podContract = PodInterface(pod); + + // Approve pod + TokenInterface tokenContract = TokenInterface(prizePoolToken); + tokenContract.approve(pod, _tokenAmount); + + podContract.depositTo(to, _tokenAmount); + + setUint(setId, _tokenAmount); + + _eventName = "LogDepositToPod(address,address,address,uint256,uint256, uint256)"; + _eventParam = abi.encode(address(prizePool), address(pod), address(to), _tokenAmount, getId, setId); + } + + /** + * @dev Withdraw from shares from Pod + * @dev The function should first withdraw from the 'float'; i.e. the funds that have not yet been deposited. + * @notice Withdraws a users share of the prize pool. + * @param pod Pod address + * @param shareAmount The number of Pod shares to burn. + * @param maxFee Max fee amount for withdrawl if amount isn't available in float. + * @param getId Get token amount at this ID from `InstaMemory` Contract. + * @param setId Set token amount at this ID in `InstaMemory` Contract. + */ + + function withdrawFromPod ( + address pod, + uint256 shareAmount, + uint256 maxFee, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _shareAmount = getUint(getId, shareAmount); + + PodInterface podContract = PodInterface(pod); + + podContract.withdraw(_shareAmount, maxFee); + + setUint(setId, _shareAmount); + + _eventName = "LogWithdrawFromPod(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(pod), _shareAmount, maxFee, getId, setId); + } } contract ConnectV2PoolTogether is PoolTogetherResolver { diff --git a/test/pooltogether/pooltogether.test.js b/test/pooltogether/pooltogether.test.js index 22f120d8..feeafa92 100644 --- a/test/pooltogether/pooltogether.test.js +++ b/test/pooltogether/pooltogether.test.js @@ -22,6 +22,7 @@ const controlledToken = "0x334cBb5858417Aee161B53Ee0D5349cCF54514CF" // PT DAI T const daiPoolFaucet = "0xF362ce295F2A4eaE4348fFC8cDBCe8d729ccb8Eb" // DAI POOL Faucet const poolTokenAddress = "0x0cEC1A9154Ff802e7934Fc916Ed7Ca50bDE6844e" const tokenFaucetProxyFactory = "0xE4E9cDB3E139D7E8a41172C20b6Ed17b6750f117" // TokenFaucetProxyFactory for claimAll +const daiPod = "0x2f994e2E4F3395649eeE8A89092e63Ca526dA829" // DAI Pod describe("PoolTogether", function () { const connectorName = "COMPOUND-TEST-A" @@ -156,7 +157,7 @@ describe("PoolTogether", function () { args: [daiPoolFaucet, dsaWallet0.address] } ] - + // Before spell // DAI balance is 0 let daiToken = await ethers.getContractAt(abis.basic.erc20, token) @@ -215,7 +216,7 @@ describe("PoolTogether", function () { args: [prizePool, dsaWallet0.address, amount, controlledToken, amount, 0, 0] } ] - + // Before spell // DAI balance is 0 let daiToken = await ethers.getContractAt(abis.basic.erc20, token) @@ -229,7 +230,7 @@ describe("PoolTogether", function () { const tokenName = await cToken.name() console.log("\tBalance before: ", balance.toString(), tokenName) - // PoolToken is 0 + // PoolToken is 0 let poolToken = await ethers.getContractAt(abis.basic.erc20, poolTokenAddress) const poolBalance = await poolToken.balanceOf(dsaWallet0.address) const poolTokenName = await poolToken.name() @@ -329,4 +330,170 @@ describe("PoolTogether", function () { expect(poolBalanceAfter).to.be.gt(ethers.utils.parseEther("0")); }); }) -}) + + describe("Main - DAI Pod Test", function() { + it("Should deposit in Pod", async function() { + const amount = ethers.utils.parseEther("99") // 99 DAI + const spells = [ + { + connector: ptConnectorName, + method: "depositToPod", + args: [prizePool, daiPod, dsaWallet0.address, amount, 0, 0] + } + ] + + // Before spell + // DAI balance is 99 + let daiToken = await ethers.getContractAt(abis.basic.erc20, token) + let daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("Before Spell:") + console.log("\tBalance before: ", daiBalance.toString(), tokens.dai.symbol); + + // PoolToken is 0 + let poolToken = await ethers.getContractAt(abis.basic.erc20, poolTokenAddress) + const poolBalance = await poolToken.balanceOf(dsaWallet0.address) + const poolTokenName = await poolToken.name() + console.log("\tBalance before: ", poolBalance.toString(), poolTokenName) + + // PodToken is 0 + let podToken = await ethers.getContractAt(abis.basic.erc20, daiPod) + const podBalance = await podToken.balanceOf(dsaWallet0.address) + const podTokenName = await podToken.name() + console.log("\tBalance before: ", podBalance.toString(), podTokenName) + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After spell + // Expect DAI balance to be less than 100, because of early withdrawal fee + daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("After spell: "); + console.log("\tBalance after: ", daiBalance.toString(), tokens.dai.symbol); + expect(daiBalance).to.be.lt(ethers.utils.parseEther("100")); + + // Expect Pool Token Balance to greater than 0 + const poolBalanceAfter = await poolToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", poolBalanceAfter.toString(), poolTokenName) + expect(poolBalanceAfter).to.be.gt(ethers.utils.parseEther("0")); + + // Expect Pod Token Balance to greater than 0 + const podBalanceAfter = await podToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", podBalanceAfter.toString(), podTokenName) + expect(podBalanceAfter).to.be.eq(ethers.utils.parseEther("99")); + }); + + it("Should wait 11 days, withdraw all podTokens, get back 99 DAI", async function () { + const amount = ethers.utils.parseEther("99") // 99 DAI + const maxFee = 0; + const spells = [ + { + connector: ptConnectorName, + method: "withdrawFromPod", + args: [daiPod, amount, maxFee, 0, 0] + } + ] + + // Before spell + // DAI balance is 0 + let daiToken = await ethers.getContractAt(abis.basic.erc20, token) + let daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("Before Spell:") + console.log("\tBalance before: ", daiBalance.toString(), tokens.dai.symbol); + + // PoolToken is 0 + let poolToken = await ethers.getContractAt(abis.basic.erc20, poolTokenAddress) + const poolBalance = await poolToken.balanceOf(dsaWallet0.address) + const poolTokenName = await poolToken.name() + console.log("\tBalance before: ", poolBalance.toString(), poolTokenName) + + // PodToken is 99 + let podToken = await ethers.getContractAt(abis.basic.erc20, daiPod) + const podBalance = await podToken.balanceOf(dsaWallet0.address) + const podTokenName = await podToken.name() + console.log("\tBalance before: ", podBalance.toString(), podTokenName) + + // Increase time by 11 days so we get back all DAI without early withdrawal fee + await ethers.provider.send("evm_increaseTime", [11*24*60*60]); + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After spell + // Expect DAI balance to be equal to 99, because of no early withdrawal fee + daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("After spell: "); + console.log("\tBalance after: ", daiBalance.toString(), tokens.dai.symbol); + expect(daiBalance).to.be.eq(ethers.utils.parseEther("99")); + + // Expect Pool Token Balance to be greater than 0 + const poolBalanceAfter = await poolToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", poolBalanceAfter.toString(), poolTokenName) + expect(poolBalanceAfter).to.be.gt(ethers.utils.parseEther("0")); + + // Expect Pod Token Balance to equal 0 + const podBalanceAfter = await podToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", podBalanceAfter.toString(), podTokenName) + expect(podBalanceAfter).to.be.eq(ethers.utils.parseEther("0")); + }); + + it("Should deposit and withdraw from pod, get back same amount of 99 DAI", async function() { + const amount = ethers.utils.parseEther("99") + const maxFee = 0; + + const spells = [ + { + connector: ptConnectorName, + method: "depositToPod", + args: [prizePool, daiPod, dsaWallet0.address, amount, 0, 0] + }, + { + connector: ptConnectorName, + method: "withdrawFromPod", + args: [daiPod, amount, maxFee, 0, 0] + } + ] + + // Before spell + // DAI balance is 0 + let daiToken = await ethers.getContractAt(abis.basic.erc20, token) + let daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("Before Spell:") + console.log("\tBalance before: ", daiBalance.toString(), tokens.dai.symbol); + + // PoolToken is greater than 0 + let poolToken = await ethers.getContractAt(abis.basic.erc20, poolTokenAddress) + const poolBalance = await poolToken.balanceOf(dsaWallet0.address) + const poolTokenName = await poolToken.name() + console.log("\tBalance before: ", poolBalance.toString(), poolTokenName) + + // PodToken is 0 + let podToken = await ethers.getContractAt(abis.basic.erc20, daiPod) + const podBalance = await podToken.balanceOf(dsaWallet0.address) + const podTokenName = await podToken.name() + console.log("\tBalance before: ", podBalance.toString(), podTokenName) + + // Run spell transaction + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + // After spell + // Expect DAI balance to be equal to 99, because funds still in 'float' + daiBalance = await daiToken.balanceOf(dsaWallet0.address); + console.log("After spell: "); + console.log("\tBalance after: ", daiBalance.toString(), tokens.dai.symbol); + expect(daiBalance).to.be.eq(ethers.utils.parseEther("99")); + + // Expect Pool Token Balance to greater than 0 + const poolBalanceAfter = await poolToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", poolBalanceAfter.toString(), poolTokenName) + expect(poolBalanceAfter).to.be.gt(ethers.utils.parseEther("0")); + + // Expect Pod Token Balance to equal 0 + const podBalanceAfter = await podToken.balanceOf(dsaWallet0.address) + console.log("\tBalance after: ", podBalanceAfter.toString(), podTokenName) + expect(podBalanceAfter).to.be.eq(ethers.utils.parseEther("0")); + }); + }) +}) \ No newline at end of file