From bba97fd082826a0adf77d32e93240cdabc5ee3b9 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Sun, 1 May 2022 17:14:52 +0800 Subject: [PATCH 01/12] add piTokenCallback to PowerIndexRouter.sol --- contracts/PowerIndexRouter.sol | 44 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/contracts/PowerIndexRouter.sol b/contracts/PowerIndexRouter.sol index fedfb28..8b014f5 100644 --- a/contracts/PowerIndexRouter.sol +++ b/contracts/PowerIndexRouter.sol @@ -55,7 +55,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { IPowerPoke public powerPoke; uint256 public reserveRatio; uint256 public claimRewardsInterval; - uint256 public lastRebalancedAt; + uint256 public lastRebalancedByPokerAt; uint256 public reserveRatioLowerBound; uint256 public reserveRatioUpperBound; // 1 ether == 100% @@ -96,6 +96,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 maxInterval; uint256 piTokenUnderlyingBalance; bool atLeastOneForceRebalance; + bool skipCanPokeCheck; } modifier onlyEOA() { @@ -294,6 +295,11 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { require(success, string(result)); } + function piTokenCallback(address sender, uint256 _withdrawAmount) external payable virtual override { + PokeFromState memory state = PokeFromState(0, 0, 0, false, true); + _rebalance(state, true, false); + } + /** * @notice Call poke by Reporter. * @param _reporterId Reporter ID. @@ -360,13 +366,22 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { * @param _isSlasher Calling by Slasher. */ function _pokeFrom(bool _claimAndDistributeRewards, bool _isSlasher) internal { - PokeFromState memory state = PokeFromState(0, 0, 0, false); + PokeFromState memory state = PokeFromState(0, 0, 0, false, false); (state.minInterval, state.maxInterval) = _getMinMaxReportInterval(); - state.piTokenUnderlyingBalance = piToken.getUnderlyingBalance(); - (uint256[] memory stakedBalanceList, uint256 totalStakedBalance) = _getUnderlyingStakedList(); + _rebalance(state, _claimAndDistributeRewards, _isSlasher); - state.atLeastOneForceRebalance = false; + require( + _canPoke(_isSlasher, state.atLeastOneForceRebalance, state.minInterval, state.maxInterval), + "INTERVAL_NOT_REACHED_OR_NOT_FORCE" + ); + + lastRebalancedByPokerAt = block.timestamp; + } + + function _rebalance(PokeFromState memory s, bool _claimAndDistributeRewards, bool _isSlasher) internal { + s.piTokenUnderlyingBalance = piToken.getUnderlyingBalance(); + (uint256[] memory stakedBalanceList, uint256 totalStakedBalance) = _getUnderlyingStakedList(); RebalanceConfig[] memory configs = new RebalanceConfig[](connectors.length); @@ -377,19 +392,19 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { } (StakeStatus status, uint256 diff, bool shouldClaim, bool forceRebalance) = getStakeAndClaimStatus( - state.piTokenUnderlyingBalance, + s.piTokenUnderlyingBalance, totalStakedBalance, stakedBalanceList[i], _claimAndDistributeRewards, connectors[i] ); if (forceRebalance) { - state.atLeastOneForceRebalance = true; + s.atLeastOneForceRebalance = true; } if (status == StakeStatus.EXCESS) { // Calling rebalance immediately if interval conditions reached - if (_canPoke(_isSlasher, forceRebalance, state.minInterval, state.maxInterval)) { + if (s.skipCanPokeCheck || _canPoke(_isSlasher, forceRebalance, s.minInterval, s.maxInterval)) { _rebalancePokeByConf(RebalanceConfig(false, status, diff, shouldClaim, forceRebalance, i)); } } else { @@ -398,23 +413,16 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { } } - require( - _canPoke(_isSlasher, state.atLeastOneForceRebalance, state.minInterval, state.maxInterval), - "INTERVAL_NOT_REACHED_OR_NOT_FORCE" - ); - // Second cycle: connectors with EQUILIBRIUM and SHORTAGE balance status on staking for (uint256 i = 0; i < connectors.length; i++) { if (!configs[i].shouldPushFunds) { continue; } // Calling rebalance if interval conditions reached - if (_canPoke(_isSlasher, configs[i].forceRebalance, state.minInterval, state.maxInterval)) { + if (s.skipCanPokeCheck || _canPoke(_isSlasher, configs[i].forceRebalance, s.minInterval, s.maxInterval)) { _rebalancePokeByConf(configs[i]); } } - - lastRebalancedAt = block.timestamp; } /** @@ -431,8 +439,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { } return _isSlasher - ? (lastRebalancedAt + _maxInterval < block.timestamp) - : (lastRebalancedAt + _minInterval < block.timestamp); + ? (lastRebalancedByPokerAt + _maxInterval < block.timestamp) + : (lastRebalancedByPokerAt + _minInterval < block.timestamp); } /** From 18141d7e55e749fe7ec0044ef57622607e70e35d Mon Sep 17 00:00:00 2001 From: defi-dev Date: Sun, 1 May 2022 18:18:24 +0800 Subject: [PATCH 02/12] addToExpectedAmount param for piTokenCallback --- contracts/PowerIndexRouter.sol | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/contracts/PowerIndexRouter.sol b/contracts/PowerIndexRouter.sol index 8b014f5..e450c36 100644 --- a/contracts/PowerIndexRouter.sol +++ b/contracts/PowerIndexRouter.sol @@ -95,6 +95,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 minInterval; uint256 maxInterval; uint256 piTokenUnderlyingBalance; + uint256 addToExpectedAmount; bool atLeastOneForceRebalance; bool skipCanPokeCheck; } @@ -296,8 +297,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { } function piTokenCallback(address sender, uint256 _withdrawAmount) external payable virtual override { - PokeFromState memory state = PokeFromState(0, 0, 0, false, true); - _rebalance(state, true, false); + PokeFromState memory state = PokeFromState(0, 0, 0, _withdrawAmount, false, true); + _rebalance(state, false, false); } /** @@ -366,7 +367,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { * @param _isSlasher Calling by Slasher. */ function _pokeFrom(bool _claimAndDistributeRewards, bool _isSlasher) internal { - PokeFromState memory state = PokeFromState(0, 0, 0, false, false); + PokeFromState memory state = PokeFromState(0, 0, 0, 0, false, false); (state.minInterval, state.maxInterval) = _getMinMaxReportInterval(); _rebalance(state, _claimAndDistributeRewards, _isSlasher); @@ -395,6 +396,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { s.piTokenUnderlyingBalance, totalStakedBalance, stakedBalanceList[i], + s.addToExpectedAmount, _claimAndDistributeRewards, connectors[i] ); @@ -572,13 +574,14 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { bool forceRebalance ) { - return getStakeStatus(piToken.getUnderlyingBalance(), getUnderlyingStaked(), _stakedBalance, _share); + return getStakeStatus(piToken.getUnderlyingBalance(), getUnderlyingStaked(), _stakedBalance, 0, _share); } function getStakeAndClaimStatus( uint256 _leftOnPiTokenBalance, uint256 _totalStakedBalance, uint256 _stakedBalance, + uint256 _addToExpectedAmount, bool _claimAndDistributeRewards, Connector memory _c ) @@ -595,6 +598,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { _leftOnPiTokenBalance, _totalStakedBalance, _stakedBalance, + _addToExpectedAmount, _c.share ); shouldClaim = _claimAndDistributeRewards && claimRewardsIntervalReached(_c.lastClaimRewardsAt); @@ -614,6 +618,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 _leftOnPiTokenBalance, uint256 _totalStakedBalance, uint256 _stakedBalance, + uint256 _addToExpectedAmount, uint256 _share ) public @@ -630,7 +635,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { _leftOnPiTokenBalance, _totalStakedBalance, _stakedBalance, - _share + _share, + _addToExpectedAmount ); if (status == StakeStatus.EQUILIBRIUM) { @@ -762,7 +768,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 _leftOnPiToken, uint256 _totalStakedBalance, uint256 _stakedBalance, - uint256 _share + uint256 _share, + uint256 _addToExpectedStakeAmount ) public view @@ -774,6 +781,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { { require(_reserveRatioPct <= HUNDRED_PCT, "RR_GREATER_THAN_100_PCT"); expectedStakeAmount = getExpectedStakeAmount(_reserveRatioPct, _leftOnPiToken, _totalStakedBalance, _share); + expectedStakeAmount = expectedStakeAmount.add(_addToExpectedStakeAmount.mul(_share).div(1 ether)); if (expectedStakeAmount > _stakedBalance) { status = StakeStatus.SHORTAGE; From 56c2755983ba5a11844cef303d4f9bced7f98427 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Sun, 1 May 2022 20:19:23 +0800 Subject: [PATCH 03/12] add reserveRatio 0 condition and test --- .solhint.json | 2 +- contracts/PowerIndexRouter.sol | 17 ++- test/implementations/TornConnector.unit.js | 135 ++++++++++++++++++++- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/.solhint.json b/.solhint.json index 8939f9c..519f4a1 100644 --- a/.solhint.json +++ b/.solhint.json @@ -2,7 +2,7 @@ "extends": "solhint:recommended", "plugins": ["prettier"], "rules": { - "code-complexity": ["error", 10], + "code-complexity": ["error", 11], "compiler-version": ["error", "^0.6.0"], "constructor-syntax": "error", "max-line-length": ["error", 120], diff --git a/contracts/PowerIndexRouter.sol b/contracts/PowerIndexRouter.sol index e450c36..c877a34 100644 --- a/contracts/PowerIndexRouter.sol +++ b/contracts/PowerIndexRouter.sol @@ -296,7 +296,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { require(success, string(result)); } - function piTokenCallback(address sender, uint256 _withdrawAmount) external payable virtual override { + function piTokenCallback(address, uint256 _withdrawAmount) external payable virtual override { PokeFromState memory state = PokeFromState(0, 0, 0, _withdrawAmount, false, true); _rebalance(state, false, false); } @@ -380,7 +380,20 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { lastRebalancedByPokerAt = block.timestamp; } - function _rebalance(PokeFromState memory s, bool _claimAndDistributeRewards, bool _isSlasher) internal { + function _rebalance( + PokeFromState memory s, + bool _claimAndDistributeRewards, + bool _isSlasher + ) internal { + if (connectors.length == 1 && reserveRatio == 0 && !_claimAndDistributeRewards) { + if (s.addToExpectedAmount > 0) { + _rebalancePoke(connectors[0], StakeStatus.EXCESS, s.addToExpectedAmount); + } else { + _rebalancePoke(connectors[0], StakeStatus.SHORTAGE, piToken.getUnderlyingBalance()); + } + return; + } + s.piTokenUnderlyingBalance = piToken.getUnderlyingBalance(); (uint256[] memory stakedBalanceList, uint256 totalStakedBalance) = _getUnderlyingStakedList(); diff --git a/test/implementations/TornConnector.unit.js b/test/implementations/TornConnector.unit.js index 5971ab4..5f3c3eb 100644 --- a/test/implementations/TornConnector.unit.js +++ b/test/implementations/TornConnector.unit.js @@ -1,8 +1,13 @@ const { time, constants, expectRevert } = require('@openzeppelin/test-helpers'); -const { ether, fromEther } = require('./../helpers'); +const { ether, fromEther, latestBlockTimestamp } = require('./../helpers'); const { buildBasicRouterConfig } = require('./../helpers/builders'); +const { + Eip2612PermitUtils, + Web3ProviderConnector, fromRpcSig, +} = require('@1inch/permit-signed-approvals-utils'); + const assert = require('chai').assert; -const MockERC20 = artifacts.require('MockERC20'); +const MockERC20 = artifacts.require('MockERC20Permit'); const TornPowerIndexConnector = artifacts.require('MockTornPowerIndexConnector'); const PowerIndexRouter = artifacts.require('PowerIndexRouter'); const WrappedPiErc20 = artifacts.require('WrappedPiErc20'); @@ -16,10 +21,12 @@ MockERC20.numberFormat = 'String'; TornPowerIndexConnector.numberFormat = 'String'; WrappedPiErc20.numberFormat = 'String'; PowerIndexRouter.numberFormat = 'String'; +TornGovernance.numberFormat = 'String'; const { web3 } = MockERC20; const REPORTER_ID = 42; +const chainId = 31337; describe('PancakeMasterChefRouter Tests', () => { let deployer, bob, alice, piGov, stub, pvp, pool1, pool2; @@ -147,18 +154,18 @@ describe('PancakeMasterChefRouter Tests', () => { await piTorn.deposit(ether('10000'), { from: alice }); // bob - await torn.transfer(bob, ether('42000')); + await torn.transfer(bob, ether('100000')); await torn.approve(governance.address, ether('42000'), { from: bob }); await governance.lockWithApproval(ether('42000'), { from: bob }); + }); + it('should claim rewards and reinvest', async () => { await myRouter.pokeFromReporter(REPORTER_ID, true, '0x'); assert.equal(await governance.lockedBalance(piTorn.address), ether(8000)); assert.equal(await torn.balanceOf(governance.address), ether(50000)); assert.equal(await torn.balanceOf(piTorn.address), ether(2000)); - }); - it('should claim rewards and reinvest', async () => { const reinvestDuration = 60 * 60; const claimParams = await connector.packClaimParams(reinvestDuration, GAS_TO_REINVEST); await myRouter.setClaimParams('0', claimParams, {from: piGov}); @@ -204,8 +211,11 @@ describe('PancakeMasterChefRouter Tests', () => { assert.equal(fromEther(await rewards.lockedProfit, ether('2.7197990668')) > 3300, true); }); - describe('on poke', async () => { + beforeEach(async () => { + await myRouter.pokeFromReporter(REPORTER_ID, true, '0x'); + }); + it('should do nothing when nothing has changed', async () => { await expectRevert(myRouter.pokeFromReporter(REPORTER_ID, false, '0x'), 'NOTHING_TO_DO'); }); @@ -241,5 +251,118 @@ describe('PancakeMasterChefRouter Tests', () => { assert.equal(await myRouter.getUnderlyingTotal(), ether('12360')); }); }); + + describe('on deposit/withdraw', async () => { + it('should increase reserve if required', async () => { + let nonce = {}; + + await myRouter.enableRouterCallback(piTorn.address, true, {from: piGov}); + await myRouter.setReserveConfig('0', '0', '0', '1000', {from: piGov}); + + assert.equal(await torn.balanceOf(piTorn.address), ether('10000')); + assert.equal(await governance.lockedBalance(piTorn.address), '0'); + + let vrs = await getPermitVrs(ether(10000), alice); + let res = await piTorn.depositWithPermit(ether(10000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: alice}); + // console.log('1 deposit by alice gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(20000)); + + await piTorn.withdraw(ether('5000'), { from: alice }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(15000)); + + vrs = await getPermitVrs(ether(2000), alice); + await piTorn.depositWithPermit(ether(2000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: alice}); + // console.log('2 deposit by alice gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(17000)); + + vrs = await getPermitVrs(ether(2000), bob); + await piTorn.depositWithPermit(ether(2000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('1 deposit by bob gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(19000)); + + vrs = await getPermitVrs(ether(1000), bob); + await piTorn.depositWithPermit(ether(1000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('2 deposit by bob gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(20000)); + + await staking.addBurnRewards(ether('50000')); + + assert.equal(await torn.balanceOf(bob), ether('55000')); + assert.equal(await piTorn.balanceOf(bob), ether('3000')); + await expectRevert(piTorn.withdraw(ether('4000'), { from: bob }), 'ERC20: burn amount exceeds balance'); + await piTorn.withdraw(ether('2000'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57000')); + assert.equal(await piTorn.balanceOf(bob), ether('1000')); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(18000)); + + res = await myRouter.pokeFromReporter(REPORTER_ID, true, '0x'); + const distributeRewards = TornPowerIndexConnector.decodeLogs(res.receipt.rawLogs).filter( + l => l.event === 'DistributeReward', + )[0]; + assert.equal(distributeRewards.args.totalReward, ether('5000')); + + assert.equal(await torn.balanceOf(governance.address), ether(64250)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(22250)); + assert.equal(await myRouter.getUnderlyingStaked(), ether(22250)); + assert.equal(await myRouter.getUnderlyingReserve(), ether('0')); + assert.equal(await myRouter.getUnderlyingAvailable(), ether(18000)); + assert.equal(await myRouter.getUnderlyingTotal(), ether('22250')); + assert.equal(await myRouter.calculateLockedProfit(), ether('4250')); + assert.equal(await torn.balanceOf(piTorn.address), ether('0')); + + await piTorn.withdraw(ether('100'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57100')); + assert.equal(await piTorn.balanceOf(bob), ether('900.001086099314865775')); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(22150)); + + await time.increase(time.duration.hours(10)); + await piTorn.withdraw(ether('100'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57200')); + assert.equal(await piTorn.balanceOf(bob), ether('819.188440112410509722')); + assert.equal(await myRouter.calculateLockedProfit(), ether(0)); + assert.equal(await myRouter.getUnderlyingAvailable(), ether('22050')); + assert.equal(await myRouter.getUnderlyingTotal(), ether('22050')); + + async function getPermitVrs(value, owner) { + const deadline = (await latestBlockTimestamp()) + 10; + if(!nonce[owner]) { + nonce[owner] = 0; + } + const permitParams = { + spender: piTorn.address, + nonce: nonce[owner], + owner, + value, + deadline, + }; + nonce[owner]++; + + const connector = new Web3ProviderConnector(web3); + const eip2612PermitUtils = new Eip2612PermitUtils(connector); + const signature = await eip2612PermitUtils.buildPermitSignature( + permitParams, + chainId, + 'Torn', + torn.address, + '1' + ); + return { + ...fromRpcSig(signature), + deadline + }; + } + }); + }); }); }); From bfb3a91d97f72b2ce28a293dddb3044a18540862 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Sun, 1 May 2022 21:38:50 +0800 Subject: [PATCH 04/12] redeployTornRouter task --- hardhat.config.js | 1 + tasks/deployTornVault.js | 2 +- tasks/redeployTornRouter.js | 168 ++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tasks/redeployTornRouter.js diff --git a/hardhat.config.js b/hardhat.config.js index 67d9003..f700acc 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -5,6 +5,7 @@ require('solidity-coverage'); require('hardhat-contract-sizer'); require('hardhat-gas-reporter'); require('./tasks/deployTornVault'); +require('./tasks/redeployTornRouter'); const fs = require('fs'); const homeDir = require('os').homedir(); diff --git a/tasks/deployTornVault.js b/tasks/deployTornVault.js index 595a21c..e55241e 100644 --- a/tasks/deployTornVault.js +++ b/tasks/deployTornVault.js @@ -1,7 +1,7 @@ require('@nomiclabs/hardhat-truffle5'); require('@nomiclabs/hardhat-ethers'); -task('deploy-torn-vault', 'Deploy VestedLpMining').setAction(async (__, {ethers, network}) => { +task('deploy-torn-vault', 'Deploy TornVault').setAction(async (__, {ethers, network}) => { const {ether, fromEther, impersonateAccount, gwei, increaseTime, advanceBlocks} = require('../test/helpers'); const WrappedPiErc20 = await artifacts.require('WrappedPiErc20'); const IERC20 = await artifacts.require(process.env.FLAT ? 'flatten/PowerIndexRouter.sol:IERC20' : '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20'); diff --git a/tasks/redeployTornRouter.js b/tasks/redeployTornRouter.js new file mode 100644 index 0000000..f179621 --- /dev/null +++ b/tasks/redeployTornRouter.js @@ -0,0 +1,168 @@ +require('@nomiclabs/hardhat-truffle5'); +require('@nomiclabs/hardhat-ethers'); + +task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers, network}) => { + const {ether, fromEther, impersonateAccount, gwei, increaseTime, advanceBlocks} = require('../test/helpers'); + const WrappedPiErc20 = await artifacts.require('WrappedPiErc20'); + const IERC20 = await artifacts.require(process.env.FLAT ? 'flatten/PowerIndexRouter.sol:IERC20' : '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20'); + const PowerIndexRouter = await artifacts.require('PowerIndexRouter'); + const TornPowerIndexConnector = await artifacts.require('TornPowerIndexConnector'); + + if (process.env.FORK) { + await ethers.provider.send('hardhat_reset', [{forking: {jsonRpcUrl: process.env.FORK}}]); + } + + const { web3 } = WrappedPiErc20; + + const [deployer] = await web3.eth.getAccounts(); + console.log('deployer', deployer); + const sendOptions = { from: deployer }; + + const OWNER = '0xB258302C3f209491d604165549079680708581Cc'; + const piTorn = await WrappedPiErc20.at('0x65ca07a894e00b6a264c897de956cb0afb63a44b'); + const tornConnector = await TornPowerIndexConnector.at('0x4930A67E605520Fe13B3037c0f70A72848Bf3B79'); + console.log('tornConnector', tornConnector.address); + + const startBalance = fromEther(await web3.eth.getBalance(deployer)); + const tornRouter = await PowerIndexRouter.new( + piTorn.address, + { + poolRestrictions: '0x698967cA2fB85A6D9a7D2BeD4D2F6D32Bbc5fCdc', + powerPoke: '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96', + reserveRatio: '0', + reserveRatioLowerBound: '0', + reserveRatioUpperBound: '0', + claimRewardsInterval: '604800', + performanceFeeReceiver: '0xd132973eaebbd6d7ca7b88e9170f2cca058de430', + performanceFee: ether(0.003) + } + ); + console.log('tornRouter', tornRouter.address); + + await tornRouter.setConnectorList([ + { + connector: tornConnector.address, + share: ether(1), + callBeforeAfterPoke: false, + newConnector: true, + connectorIndex: 0, + }, + ]); + console.log('tornConnector done'); + + await tornRouter.transferOwnership(OWNER, sendOptions); + const endBalance = fromEther(await web3.eth.getBalance(deployer)); + console.log('balance spent', startBalance - endBalance); + if (network.name !== 'mainnetfork') { + return; + } + const PowerPoke = await artifacts.require('IPowerPoke'); + const ITornGovernance = await artifacts.require('ITornGovernance'); + const ITornStaking = await artifacts.require('ITornStaking'); + + const torn = await IERC20.at('0x77777feddddffc19ff86db637967013e6c6a116c'); + + const tornHolder = '0xf977814e90da44bfa03b6295a0616a897441acec'; + const pokerReporter = '0xabdf215fce6c5b0c1b40b9f2068204a9e7c49627'; + await impersonateAccount(ethers, tornHolder); + const amount = ether( 33400); + console.log('1 wrapper balance', fromEther(await torn.balanceOf(piTorn.address))); + await torn.approve(piTorn.address, amount, {from: tornHolder}); + await piTorn.deposit(amount, {from: tornHolder}); + console.log('2 wrapper balance', fromEther(await torn.balanceOf(piTorn.address))); + + const BONUS_NUMERATOR = '7610350076'; + const BONUS_DENUMERATOR = '10000000000000000'; + const MIN_REPORT_INTERVAL = 60 * 60 * 24 * 14; + const MAX_REPORT_INTERVAL = MIN_REPORT_INTERVAL + 60 * 60; + const MAX_GAS_PRICE = gwei(500); + const PER_GAS = '10000'; + const MIN_SLASHING_DEPOSIT = ether(40); + + await impersonateAccount(ethers, OWNER); + const oldRouter = await PowerIndexRouter.at('0x0d0B8d93D9F099A0cB2e2dFE8362e88cf08C3094'); + await oldRouter.migrateToNewRouter(piTorn.address, tornRouter.address, [], {from: OWNER}); + await tornRouter.initRouterByConnector('0', '0x', {from: OWNER}); + + const powerPokeAddress = '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96'; + const cvpAddress = '0x38e4adb44ef08f22f5b5b76a8f0c2d0dcbe7dca1'; + const powerPoke = await PowerPoke.at(powerPokeAddress); + await powerPoke.addClient(tornRouter.address, OWNER, true, MAX_GAS_PRICE, MIN_REPORT_INTERVAL, MAX_REPORT_INTERVAL, {from: OWNER}); + await powerPoke.setMinimalDeposit(tornRouter.address, MIN_SLASHING_DEPOSIT, {from: OWNER}); + await powerPoke.setBonusPlan(tornRouter.address, '1', true, BONUS_NUMERATOR, BONUS_DENUMERATOR, PER_GAS, {from: OWNER}); + await powerPoke.setFixedCompensations(tornRouter.address, 200000, 60000, {from: OWNER}); + + const cvp = await IERC20.at(cvpAddress); + await cvp.approve(powerPoke.address, ether(10000), {from: OWNER}); + await powerPoke.addCredit(tornRouter.address, ether(10000), {from: OWNER}); + + const powerPokeOpts = web3.eth.abi.encodeParameter( + { PowerPokeRewardOpts: {to: 'address', compensateInETH: 'bool'} }, + {to: pokerReporter, compensateInETH: true}, + ); + + await impersonateAccount(ethers, pokerReporter); + + await tornRouter.pokeFromReporter('1', false, powerPokeOpts, {from: pokerReporter}); + + console.log('3 wrapper balance', fromEther(await torn.balanceOf(piTorn.address))); + const TORN_STAKING = '0x2fc93484614a34f26f7970cbb94615ba109bb4bf'; + const TORN_GOVERNANCE = '0x5efda50f22d34f262c29268506c5fa42cb56a1ce'; + const governance = await ITornGovernance.at(TORN_GOVERNANCE); + const staking = await ITornStaking.at(TORN_STAKING); + console.log('lockedBalance', fromEther(await governance.lockedBalance(piTorn.address))); + console.log('checkReward 1', fromEther(await staking.checkReward(piTorn.address))); + + const TEN_HOURS = 60 * 60 * 10; + const GAS_TO_REINVEST = '100000'; + await impersonateAccount(ethers, TORN_GOVERNANCE); + + await tornRouter.setClaimParams('0', await getClaimParams(TEN_HOURS), {from: OWNER}); + + await increaseTime(TEN_HOURS); + await advanceBlocks(1); + await staking.addBurnRewards(ether(1700), {from: TORN_GOVERNANCE}); + console.log('checkReward 2', fromEther(await staking.checkReward(piTorn.address))); + await printForecast(TEN_HOURS); + await checkClaimAvailability(TEN_HOURS); + + await increaseTime(TEN_HOURS); + await advanceBlocks(1); + await staking.addBurnRewards(ether(2700), {from: TORN_GOVERNANCE}); + console.log('checkReward 3', fromEther(await staking.checkReward(piTorn.address))); + await printForecast(TEN_HOURS); + await checkClaimAvailability(TEN_HOURS); + + const res = await tornRouter.pokeFromReporter('1', true, powerPokeOpts, {from: pokerReporter}); + console.log('res.receipt.gasUsed', res.receipt.gasUsed); + + console.log('lockedBalance', fromEther(await governance.lockedBalance(piTorn.address))); + + function getClaimParams(duration) { + return tornConnector.packClaimParams(duration, GAS_TO_REINVEST); + } + async function checkClaimAvailability(duration) { + const connector = await tornRouter.connectors('0'); + const claimParams = await getClaimParams(duration); + const res = await tornConnector.isClaimAvailable(claimParams, connector.lastClaimRewardsAt, connector.lastChangeStakeAt); + const tornNeedToReinvest = await tornConnector.getTornUsedToReinvest(GAS_TO_REINVEST, parseInt(process.env.GAS_PRICE) * 10 ** 9); + console.log('tornNeedToReinvest', fromEther(tornNeedToReinvest)); + console.log('isClaimAvailable for', parseInt(duration) / (60 * 60), 'hours:', res); + return res; + } + + async function printForecast(investDuration) { + const block = await web3.eth.getBlock('latest'); + const connector = await tornRouter.connectors('0'); + let {lastClaimRewardsAt, lastChangeStakeAt} = connector; + lastClaimRewardsAt = parseInt(lastClaimRewardsAt.toString(10)); + lastChangeStakeAt = parseInt(lastChangeStakeAt.toString(10)); + const lastRewardsAt = lastClaimRewardsAt > lastChangeStakeAt ? lastClaimRewardsAt : lastChangeStakeAt; + + console.log('forecast after', (block.timestamp - lastRewardsAt) / (60 * 60), 'hours:', fromEther(await tornConnector.getPendingAndForecastReward( + lastClaimRewardsAt, + lastChangeStakeAt, + investDuration + ).then(r => r.forecastByPending)), 'with invest duration', investDuration / (60 * 60), 'hours'); + } +}); From e210cdc143dd981cd8ad9a9af46d8f2436cf7ccc Mon Sep 17 00:00:00 2001 From: defi-dev Date: Mon, 2 May 2022 22:39:36 +0800 Subject: [PATCH 05/12] update redeploy-torn-router --- tasks/redeployTornRouter.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tasks/redeployTornRouter.js b/tasks/redeployTornRouter.js index f179621..2a7aa71 100644 --- a/tasks/redeployTornRouter.js +++ b/tasks/redeployTornRouter.js @@ -19,25 +19,27 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers const sendOptions = { from: deployer }; const OWNER = '0xB258302C3f209491d604165549079680708581Cc'; - const piTorn = await WrappedPiErc20.at('0x65ca07a894e00b6a264c897de956cb0afb63a44b'); - const tornConnector = await TornPowerIndexConnector.at('0x4930A67E605520Fe13B3037c0f70A72848Bf3B79'); - console.log('tornConnector', tornConnector.address); + const piTornAddress = '0x65ca07a894e00b6a264c897de956cb0afb63a44b'; + const tornConnectorAddress = '0x4930A67E605520Fe13B3037c0f70A72848Bf3B79'; const startBalance = fromEther(await web3.eth.getBalance(deployer)); const tornRouter = await PowerIndexRouter.new( - piTorn.address, + piTornAddress, { poolRestrictions: '0x698967cA2fB85A6D9a7D2BeD4D2F6D32Bbc5fCdc', powerPoke: '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96', reserveRatio: '0', reserveRatioLowerBound: '0', reserveRatioUpperBound: '0', - claimRewardsInterval: '604800', + claimRewardsInterval: '86400', performanceFeeReceiver: '0xd132973eaebbd6d7ca7b88e9170f2cca058de430', - performanceFee: ether(0.003) + performanceFee: '0' } ); console.log('tornRouter', tornRouter.address); + const piTorn = await WrappedPiErc20.at(piTornAddress); + const tornConnector = await TornPowerIndexConnector.at(tornConnectorAddress); + console.log('tornConnector', tornConnector.address); await tornRouter.setConnectorList([ { @@ -48,6 +50,8 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers connectorIndex: 0, }, ]); + + await tornRouter.setClaimParams('0', await tornConnector.contract.methods.packClaimParams('2592000', '564341').call({}), sendOptions); console.log('tornConnector done'); await tornRouter.transferOwnership(OWNER, sendOptions); From f50848eb9db72515356b87321252dd927991d85b Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 3 May 2022 00:33:27 +0800 Subject: [PATCH 06/12] add routerCallbackEnabled condition to withdrawShares --- contracts/WrappedPiErc20.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/WrappedPiErc20.sol b/contracts/WrappedPiErc20.sol index 426d71a..b475907 100644 --- a/contracts/WrappedPiErc20.sol +++ b/contracts/WrappedPiErc20.sol @@ -157,7 +157,10 @@ contract WrappedPiErc20 is ERC20, ReentrancyGuard, WrappedPiErc20Interface { uint256 withdrawAmount = getUnderlyingEquivalentForPi(_burnAmount); require(withdrawAmount > 0, "ZERO_UNDERLYING_TO_WITHDRAW"); - PowerIndexNaiveRouterInterface(router).piTokenCallback{ value: msg.value }(msg.sender, withdrawAmount); + + if (routerCallbackEnabled) { + PowerIndexNaiveRouterInterface(router).piTokenCallback{ value: msg.value }(msg.sender, withdrawAmount); + } _burn(msg.sender, _burnAmount); underlying.safeTransfer(msg.sender, withdrawAmount); From aef7dd2e5042a5a8ea5dbb25146ee79f5bd8698a Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 3 May 2022 19:31:00 +0800 Subject: [PATCH 07/12] update deploy --- package.json | 2 +- tasks/deployTornVault.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f7bd0d1..92c3e29 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build": "yarn run compile && yarn run typechain", "clean": "hardhat clean", "compile": "hardhat compile", - "compile-release": "COMPILE_TARGET=release hardhat compile", + "compile-release": "rm -rf ./artifacts && COMPILE_TARGET=release hardhat compile", "coverage": "hardhat coverage --show-stack-traces --solcoverjs ./.solcover.js --network coverage --temp artifacts --testfiles \"./test/**/*.js\"", "lint:sol": "solhint --config ./.solhint.json \"contracts/**/*.sol\"", "lint:js": "eslint --config .eslintrc.json --ignore-path ./.eslintignore --ext .js .", diff --git a/tasks/deployTornVault.js b/tasks/deployTornVault.js index e55241e..2237996 100644 --- a/tasks/deployTornVault.js +++ b/tasks/deployTornVault.js @@ -4,7 +4,7 @@ require('@nomiclabs/hardhat-ethers'); task('deploy-torn-vault', 'Deploy TornVault').setAction(async (__, {ethers, network}) => { const {ether, fromEther, impersonateAccount, gwei, increaseTime, advanceBlocks} = require('../test/helpers'); const WrappedPiErc20 = await artifacts.require('WrappedPiErc20'); - const IERC20 = await artifacts.require(process.env.FLAT ? 'flatten/PowerIndexRouter.sol:IERC20' : '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20'); + const IERC20 = await artifacts.require('WrappedPiErc20'); const PowerIndexRouter = await artifacts.require('PowerIndexRouter'); const TornPowerIndexConnector = await artifacts.require('TornPowerIndexConnector'); @@ -25,19 +25,19 @@ task('deploy-torn-vault', 'Deploy TornVault').setAction(async (__, {ethers, netw const tornAddress = '0x77777feddddffc19ff86db637967013e6c6a116c'; const startBalance = fromEther(await web3.eth.getBalance(deployer)); const piTorn = await WrappedPiErc20.new(tornAddress, deployer, 'PowerPool Torn Vault', 'ppTORN'); - console.log('piTorn', piTorn.address, 'name', await piTorn.name(), 'symbol', await piTorn.symbol()); + console.log('piTorn', piTorn.address, 'name', await piTorn.contract.methods.name().call(), 'symbol', await piTorn.contract.methods.symbol().call()); const tornRouter = await PowerIndexRouter.new( piTorn.address, { poolRestrictions: '0x698967cA2fB85A6D9a7D2BeD4D2F6D32Bbc5fCdc', powerPoke: '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96', - reserveRatio: ether(0.1), - reserveRatioLowerBound: ether(0.01), - reserveRatioUpperBound: ether(0.2), - claimRewardsInterval: '604800', + reserveRatio: '0', + reserveRatioLowerBound: '0', + reserveRatioUpperBound: '0', + claimRewardsInterval: '86400', performanceFeeReceiver: '0xd132973eaebbd6d7ca7b88e9170f2cca058de430', - performanceFee: ether(0.003) + performanceFee: '0' } ); console.log('tornRouter', tornRouter.address); @@ -55,10 +55,11 @@ task('deploy-torn-vault', 'Deploy TornVault').setAction(async (__, {ethers, netw connectorIndex: 0, }, ]); + await piTorn.enableRouterCallback(true); await piTorn.changeRouter(tornRouter.address); console.log('tornConnector done'); // console.log('getUnderlyingReserve', await tornRouter.getUnderlyingReserve()); - console.log('tornConnector.getTornPriceRatio', await tornConnector.getTornPriceRatio().then(r => r.toString())); + // console.log('tornConnector.getTornPriceRatio', await tornConnector.contract.methods.getTornPriceRatio().call().then(r => r.toString())); // console.log('router.getUnderlyingStaked', await tornRouter.getUnderlyingStaked()); // console.log('calculateLockedProfit', await tornRouter.calculateLockedProfit()); // console.log('getUnderlyingAvailable', await tornRouter.getUnderlyingAvailable()); From 283320c02ad927c2e5ec074bb1c3ade86456364d Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 3 May 2022 20:23:47 +0800 Subject: [PATCH 08/12] fix wrapper test --- test/WrappedPiErc20.unit.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/WrappedPiErc20.unit.test.js b/test/WrappedPiErc20.unit.test.js index bf75264..f154f08 100644 --- a/test/WrappedPiErc20.unit.test.js +++ b/test/WrappedPiErc20.unit.test.js @@ -664,6 +664,8 @@ describe('WrappedPiErc20 Unit Tests', () => { const ethFee = ether(0.001); + await router.enableRouterCallback(piCake.address, true); + await router.setPiTokenEthFee(ethFee, { from: deployer }); assert.equal(await piCake.ethFee(), ethFee); From 361f6c6ba7aaccadcae1c8a1ba694ab7fe3406b0 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 3 May 2022 21:51:23 +0800 Subject: [PATCH 09/12] rework subFromExpectedStakeAmount --- contracts/PowerIndexRouter.sol | 24 +++-- test/implementations/TornConnector.unit.js | 115 +++++++++++++++++++++ 2 files changed, 129 insertions(+), 10 deletions(-) diff --git a/contracts/PowerIndexRouter.sol b/contracts/PowerIndexRouter.sol index c877a34..7019781 100644 --- a/contracts/PowerIndexRouter.sol +++ b/contracts/PowerIndexRouter.sol @@ -95,7 +95,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 minInterval; uint256 maxInterval; uint256 piTokenUnderlyingBalance; - uint256 addToExpectedAmount; + uint256 subFromExpectedStakeAmount; bool atLeastOneForceRebalance; bool skipCanPokeCheck; } @@ -386,8 +386,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { bool _isSlasher ) internal { if (connectors.length == 1 && reserveRatio == 0 && !_claimAndDistributeRewards) { - if (s.addToExpectedAmount > 0) { - _rebalancePoke(connectors[0], StakeStatus.EXCESS, s.addToExpectedAmount); + if (s.subFromExpectedStakeAmount > 0) { + _rebalancePoke(connectors[0], StakeStatus.EXCESS, s.subFromExpectedStakeAmount); } else { _rebalancePoke(connectors[0], StakeStatus.SHORTAGE, piToken.getUnderlyingBalance()); } @@ -409,7 +409,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { s.piTokenUnderlyingBalance, totalStakedBalance, stakedBalanceList[i], - s.addToExpectedAmount, + s.subFromExpectedStakeAmount, _claimAndDistributeRewards, connectors[i] ); @@ -594,7 +594,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 _leftOnPiTokenBalance, uint256 _totalStakedBalance, uint256 _stakedBalance, - uint256 _addToExpectedAmount, + uint256 _subFromExpectedStakeAmount, bool _claimAndDistributeRewards, Connector memory _c ) @@ -611,7 +611,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { _leftOnPiTokenBalance, _totalStakedBalance, _stakedBalance, - _addToExpectedAmount, + _subFromExpectedStakeAmount, _c.share ); shouldClaim = _claimAndDistributeRewards && claimRewardsIntervalReached(_c.lastClaimRewardsAt); @@ -631,7 +631,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 _leftOnPiTokenBalance, uint256 _totalStakedBalance, uint256 _stakedBalance, - uint256 _addToExpectedAmount, + uint256 _subFromExpectedStakeAmount, uint256 _share ) public @@ -649,7 +649,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { _totalStakedBalance, _stakedBalance, _share, - _addToExpectedAmount + _subFromExpectedStakeAmount ); if (status == StakeStatus.EQUILIBRIUM) { @@ -782,7 +782,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { uint256 _totalStakedBalance, uint256 _stakedBalance, uint256 _share, - uint256 _addToExpectedStakeAmount + uint256 _subFromExpectedStakeAmount ) public view @@ -794,7 +794,11 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter { { require(_reserveRatioPct <= HUNDRED_PCT, "RR_GREATER_THAN_100_PCT"); expectedStakeAmount = getExpectedStakeAmount(_reserveRatioPct, _leftOnPiToken, _totalStakedBalance, _share); - expectedStakeAmount = expectedStakeAmount.add(_addToExpectedStakeAmount.mul(_share).div(1 ether)); + + uint256 subFromExpectedStakeAmountByShare = _subFromExpectedStakeAmount.mul(_share).div(1 ether); + if (expectedStakeAmount >= subFromExpectedStakeAmountByShare) { + expectedStakeAmount -= subFromExpectedStakeAmountByShare; + } if (expectedStakeAmount > _stakedBalance) { status = StakeStatus.SHORTAGE; diff --git a/test/implementations/TornConnector.unit.js b/test/implementations/TornConnector.unit.js index 5f3c3eb..5b9f634 100644 --- a/test/implementations/TornConnector.unit.js +++ b/test/implementations/TornConnector.unit.js @@ -364,5 +364,120 @@ describe('PancakeMasterChefRouter Tests', () => { } }); }); + + describe('on deposit/withdraw', async () => { + it('should increase reserve if required', async () => { + let nonce = {}; + + await myRouter.enableRouterCallback(piTorn.address, true, {from: piGov}); + await myRouter.setReserveConfig(ether('0.1'), ether('0.1'), ether('0.1'), '1000', {from: piGov}); + + assert.equal(await torn.balanceOf(piTorn.address), ether('10000')); + assert.equal(await governance.lockedBalance(piTorn.address), '0'); + + let vrs = await getPermitVrs(ether(10000), alice); + await piTorn.depositWithPermit(ether(10000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: alice}); + // console.log('1 deposit by alice gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), ether(2000)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(18000)); + assert.equal(await torn.balanceOf(alice), '0'); + + await piTorn.withdraw(ether('5000'), { from: alice }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(alice), ether('5000')); + assert.equal(await torn.balanceOf(piTorn.address), ether(2000)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(13000)); + + vrs = await getPermitVrs(ether(2000), alice); + await piTorn.depositWithPermit(ether(2000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: alice}); + // console.log('2 deposit by alice gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), ether(1700)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(15300)); + + vrs = await getPermitVrs(ether(2000), bob); + await piTorn.depositWithPermit(ether(2000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('1 deposit by bob gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), ether(1900)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(17100)); + + vrs = await getPermitVrs(ether(1000), bob); + await piTorn.depositWithPermit(ether(1000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('2 deposit by bob gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), ether(2000)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(18000)); + + await staking.addBurnRewards(ether('50000')); + + assert.equal(await torn.balanceOf(bob), ether('55000')); + assert.equal(await piTorn.balanceOf(bob), ether('3000')); + await expectRevert(piTorn.withdraw(ether('4000'), { from: bob }), 'ERC20: burn amount exceeds balance'); + await piTorn.withdraw(ether('2000'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57000')); + assert.equal(await piTorn.balanceOf(bob), ether('1000')); + assert.equal(await torn.balanceOf(piTorn.address), ether(2000)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(16000)); + + const res = await myRouter.pokeFromReporter(REPORTER_ID, true, '0x'); + const distributeRewards = TornPowerIndexConnector.decodeLogs(res.receipt.rawLogs).filter( + l => l.event === 'DistributeReward', + )[0]; + assert.equal(distributeRewards.args.totalReward, ether('4500')); + + assert.equal(await torn.balanceOf(governance.address), ether(62025)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(20025)); + assert.equal(await myRouter.getUnderlyingStaked(), ether(20025)); + assert.equal(await myRouter.getUnderlyingReserve(), ether(1800)); + assert.equal(await myRouter.getUnderlyingAvailable(), ether(18000)); + assert.equal(await myRouter.getUnderlyingTotal(), ether('21825')); + assert.equal(await myRouter.calculateLockedProfit(), ether('3825')); + assert.equal(await torn.balanceOf(piTorn.address), ether(1800)); + + await piTorn.withdraw(ether('100'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57100')); + assert.equal(await piTorn.balanceOf(bob), ether('900.000977490445030900')); + assert.equal(await torn.balanceOf(piTorn.address), ether(2182.5)); + assert.equal(await governance.lockedBalance(piTorn.address), ether(19542.5)); + + await time.increase(time.duration.hours(10)); + await piTorn.withdraw(ether('100'), { from: bob }); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57200')); + assert.equal(await piTorn.balanceOf(bob), ether('817.607417179787056075')); + assert.equal(await myRouter.calculateLockedProfit(), ether(0)); + assert.equal(await myRouter.getUnderlyingAvailable(), ether('21625')); + assert.equal(await myRouter.getUnderlyingTotal(), ether('21625')); + + async function getPermitVrs(value, owner) { + const deadline = (await latestBlockTimestamp()) + 10; + if(!nonce[owner]) { + nonce[owner] = 0; + } + const permitParams = { + spender: piTorn.address, + nonce: nonce[owner], + owner, + value, + deadline, + }; + nonce[owner]++; + + const connector = new Web3ProviderConnector(web3); + const eip2612PermitUtils = new Eip2612PermitUtils(connector); + const signature = await eip2612PermitUtils.buildPermitSignature( + permitParams, + chainId, + 'Torn', + torn.address, + '1' + ); + return { + ...fromRpcSig(signature), + deadline + }; + } + }); + }); }); }); From 0d758a2f2290d54b193e085c2e3e4bca56ea88fc Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 3 May 2022 22:49:58 +0800 Subject: [PATCH 10/12] update redeployTornRouter.js --- tasks/redeployTornRouter.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tasks/redeployTornRouter.js b/tasks/redeployTornRouter.js index 2a7aa71..3b83a7a 100644 --- a/tasks/redeployTornRouter.js +++ b/tasks/redeployTornRouter.js @@ -4,7 +4,7 @@ require('@nomiclabs/hardhat-ethers'); task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers, network}) => { const {ether, fromEther, impersonateAccount, gwei, increaseTime, advanceBlocks} = require('../test/helpers'); const WrappedPiErc20 = await artifacts.require('WrappedPiErc20'); - const IERC20 = await artifacts.require(process.env.FLAT ? 'flatten/PowerIndexRouter.sol:IERC20' : '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20'); + const IERC20 = await artifacts.require('WrappedPiErc20'); const PowerIndexRouter = await artifacts.require('PowerIndexRouter'); const TornPowerIndexConnector = await artifacts.require('TornPowerIndexConnector'); @@ -19,8 +19,8 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers const sendOptions = { from: deployer }; const OWNER = '0xB258302C3f209491d604165549079680708581Cc'; - const piTornAddress = '0x65ca07a894e00b6a264c897de956cb0afb63a44b'; - const tornConnectorAddress = '0x4930A67E605520Fe13B3037c0f70A72848Bf3B79'; + const piTornAddress = '0xa1ebc8bde2f1f87fe24f384497b6bd9ce3b14345'; + const tornConnectorAddress = '0x887d871b5aE02dFC35d1ba579461CbE4ed3D95b7'; const startBalance = fromEther(await web3.eth.getBalance(deployer)); const tornRouter = await PowerIndexRouter.new( @@ -84,9 +84,8 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers const MIN_SLASHING_DEPOSIT = ether(40); await impersonateAccount(ethers, OWNER); - const oldRouter = await PowerIndexRouter.at('0x0d0B8d93D9F099A0cB2e2dFE8362e88cf08C3094'); + const oldRouter = await PowerIndexRouter.at('0x0a6AA119C58cE6e7733dA6ECe7fBa5668d897c7C'); await oldRouter.migrateToNewRouter(piTorn.address, tornRouter.address, [], {from: OWNER}); - await tornRouter.initRouterByConnector('0', '0x', {from: OWNER}); const powerPokeAddress = '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96'; const cvpAddress = '0x38e4adb44ef08f22f5b5b76a8f0c2d0dcbe7dca1'; From 9d6e55902e0345d4ca4379483f9e0e226d848669 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 10 May 2022 21:53:27 +0800 Subject: [PATCH 11/12] remove approve from torn connector reinvest block, and test --- contracts/connectors/TornPowerIndexConnector.sol | 1 - test/implementations/TornConnector.unit.js | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/connectors/TornPowerIndexConnector.sol b/contracts/connectors/TornPowerIndexConnector.sol index 53b6b83..b0cddaf 100644 --- a/contracts/connectors/TornPowerIndexConnector.sol +++ b/contracts/connectors/TornPowerIndexConnector.sol @@ -46,7 +46,6 @@ contract TornPowerIndexConnector is AbstractConnector { if (receivedReward > 0) { uint256 rewardsToReinvest; (rewardsToReinvest, stakeData) = _distributeReward(_distributeData, PI_TOKEN, UNDERLYING, receivedReward); - _approveToStaking(rewardsToReinvest); _stakeImpl(rewardsToReinvest); return stakeData; } diff --git a/test/implementations/TornConnector.unit.js b/test/implementations/TornConnector.unit.js index 5b9f634..798d394 100644 --- a/test/implementations/TornConnector.unit.js +++ b/test/implementations/TornConnector.unit.js @@ -334,6 +334,14 @@ describe('PancakeMasterChefRouter Tests', () => { assert.equal(await myRouter.getUnderlyingAvailable(), ether('22050')); assert.equal(await myRouter.getUnderlyingTotal(), ether('22050')); + vrs = await getPermitVrs(ether(100), bob); + await piTorn.depositWithPermit(ether(100), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('withdraw gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(bob), ether('57100')); + assert.equal(await piTorn.balanceOf(bob), ether('900.001086099314865775')); + assert.equal(await torn.balanceOf(piTorn.address), '0'); + assert.equal(await governance.lockedBalance(piTorn.address), ether(22150)); + async function getPermitVrs(value, owner) { const deadline = (await latestBlockTimestamp()) + 10; if(!nonce[owner]) { @@ -449,6 +457,12 @@ describe('PancakeMasterChefRouter Tests', () => { assert.equal(await myRouter.getUnderlyingAvailable(), ether('21625')); assert.equal(await myRouter.getUnderlyingTotal(), ether('21625')); + vrs = await getPermitVrs(ether(1000), bob); + await piTorn.depositWithPermit(ether(1000), vrs.deadline, vrs.v, vrs.r, vrs.s, {from: bob}); + // console.log('2 deposit by bob gasUsed', res.receipt.gasUsed); + assert.equal(await torn.balanceOf(piTorn.address), ether('2262.5')); + assert.equal(await governance.lockedBalance(piTorn.address), ether('20362.5')); + async function getPermitVrs(value, owner) { const deadline = (await latestBlockTimestamp()) + 10; if(!nonce[owner]) { From b35977d943b2a80b2256ec0d660687bbc248b769 Mon Sep 17 00:00:00 2001 From: defi-dev Date: Tue, 10 May 2022 22:20:50 +0800 Subject: [PATCH 12/12] update redeployTornRouter.js --- package.json | 2 +- tasks/redeployTornRouter.js | 81 +++++++++++-------------------------- 2 files changed, 25 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 92c3e29..ce94dad 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build": "yarn run compile && yarn run typechain", "clean": "hardhat clean", "compile": "hardhat compile", - "compile-release": "rm -rf ./artifacts && COMPILE_TARGET=release hardhat compile", + "compile-release": "rm -rf ./artifacts && rm -rf ./cache && COMPILE_TARGET=release hardhat compile", "coverage": "hardhat coverage --show-stack-traces --solcoverjs ./.solcover.js --network coverage --temp artifacts --testfiles \"./test/**/*.js\"", "lint:sol": "solhint --config ./.solhint.json \"contracts/**/*.sol\"", "lint:js": "eslint --config .eslintrc.json --ignore-path ./.eslintignore --ext .js .", diff --git a/tasks/redeployTornRouter.js b/tasks/redeployTornRouter.js index 3b83a7a..a9cd7ef 100644 --- a/tasks/redeployTornRouter.js +++ b/tasks/redeployTornRouter.js @@ -2,7 +2,7 @@ require('@nomiclabs/hardhat-truffle5'); require('@nomiclabs/hardhat-ethers'); task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers, network}) => { - const {ether, fromEther, impersonateAccount, gwei, increaseTime, advanceBlocks} = require('../test/helpers'); + const {ether, fromEther, impersonateAccount, increaseTime, advanceBlocks} = require('../test/helpers'); const WrappedPiErc20 = await artifacts.require('WrappedPiErc20'); const IERC20 = await artifacts.require('WrappedPiErc20'); const PowerIndexRouter = await artifacts.require('PowerIndexRouter'); @@ -16,51 +16,39 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers const [deployer] = await web3.eth.getAccounts(); console.log('deployer', deployer); - const sendOptions = { from: deployer }; - const OWNER = '0xB258302C3f209491d604165549079680708581Cc'; const piTornAddress = '0xa1ebc8bde2f1f87fe24f384497b6bd9ce3b14345'; - const tornConnectorAddress = '0x887d871b5aE02dFC35d1ba579461CbE4ed3D95b7'; + const TORN_STAKING = '0x2fc93484614a34f26f7970cbb94615ba109bb4bf'; + const TORN_GOVERNANCE = '0x5efda50f22d34f262c29268506c5fa42cb56a1ce'; + const tornAddress = '0x77777feddddffc19ff86db637967013e6c6a116c'; const startBalance = fromEther(await web3.eth.getBalance(deployer)); - const tornRouter = await PowerIndexRouter.new( - piTornAddress, - { - poolRestrictions: '0x698967cA2fB85A6D9a7D2BeD4D2F6D32Bbc5fCdc', - powerPoke: '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96', - reserveRatio: '0', - reserveRatioLowerBound: '0', - reserveRatioUpperBound: '0', - claimRewardsInterval: '86400', - performanceFeeReceiver: '0xd132973eaebbd6d7ca7b88e9170f2cca058de430', - performanceFee: '0' - } - ); + const tornRouter = await PowerIndexRouter.at('0xDAf584C15722cdc7E78214Fb1A4832dA6638D655'); console.log('tornRouter', tornRouter.address); const piTorn = await WrappedPiErc20.at(piTornAddress); - const tornConnector = await TornPowerIndexConnector.at(tornConnectorAddress); + const tornConnector = await TornPowerIndexConnector.new(TORN_STAKING, tornAddress, piTorn.address, TORN_GOVERNANCE); console.log('tornConnector', tornConnector.address); + console.log('tornConnector done'); + + const endBalance = fromEther(await web3.eth.getBalance(deployer)); + console.log('balance spent', startBalance - endBalance); + if (network.name !== 'mainnetfork') { + return; + } + + await impersonateAccount(ethers, OWNER); + await tornRouter.initRouterByConnector('0', '0x', {from: OWNER}); await tornRouter.setConnectorList([ { connector: tornConnector.address, share: ether(1), callBeforeAfterPoke: false, - newConnector: true, + newConnector: false, connectorIndex: 0, }, - ]); - - await tornRouter.setClaimParams('0', await tornConnector.contract.methods.packClaimParams('2592000', '564341').call({}), sendOptions); - console.log('tornConnector done'); + ], {from: OWNER}); - await tornRouter.transferOwnership(OWNER, sendOptions); - const endBalance = fromEther(await web3.eth.getBalance(deployer)); - console.log('balance spent', startBalance - endBalance); - if (network.name !== 'mainnetfork') { - return; - } - const PowerPoke = await artifacts.require('IPowerPoke'); const ITornGovernance = await artifacts.require('ITornGovernance'); const ITornStaking = await artifacts.require('ITornStaking'); @@ -75,30 +63,6 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers await piTorn.deposit(amount, {from: tornHolder}); console.log('2 wrapper balance', fromEther(await torn.balanceOf(piTorn.address))); - const BONUS_NUMERATOR = '7610350076'; - const BONUS_DENUMERATOR = '10000000000000000'; - const MIN_REPORT_INTERVAL = 60 * 60 * 24 * 14; - const MAX_REPORT_INTERVAL = MIN_REPORT_INTERVAL + 60 * 60; - const MAX_GAS_PRICE = gwei(500); - const PER_GAS = '10000'; - const MIN_SLASHING_DEPOSIT = ether(40); - - await impersonateAccount(ethers, OWNER); - const oldRouter = await PowerIndexRouter.at('0x0a6AA119C58cE6e7733dA6ECe7fBa5668d897c7C'); - await oldRouter.migrateToNewRouter(piTorn.address, tornRouter.address, [], {from: OWNER}); - - const powerPokeAddress = '0x04D7aA22ef7181eE3142F5063e026Af1BbBE5B96'; - const cvpAddress = '0x38e4adb44ef08f22f5b5b76a8f0c2d0dcbe7dca1'; - const powerPoke = await PowerPoke.at(powerPokeAddress); - await powerPoke.addClient(tornRouter.address, OWNER, true, MAX_GAS_PRICE, MIN_REPORT_INTERVAL, MAX_REPORT_INTERVAL, {from: OWNER}); - await powerPoke.setMinimalDeposit(tornRouter.address, MIN_SLASHING_DEPOSIT, {from: OWNER}); - await powerPoke.setBonusPlan(tornRouter.address, '1', true, BONUS_NUMERATOR, BONUS_DENUMERATOR, PER_GAS, {from: OWNER}); - await powerPoke.setFixedCompensations(tornRouter.address, 200000, 60000, {from: OWNER}); - - const cvp = await IERC20.at(cvpAddress); - await cvp.approve(powerPoke.address, ether(10000), {from: OWNER}); - await powerPoke.addCredit(tornRouter.address, ether(10000), {from: OWNER}); - const powerPokeOpts = web3.eth.abi.encodeParameter( { PowerPokeRewardOpts: {to: 'address', compensateInETH: 'bool'} }, {to: pokerReporter, compensateInETH: true}, @@ -106,11 +70,9 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers await impersonateAccount(ethers, pokerReporter); - await tornRouter.pokeFromReporter('1', false, powerPokeOpts, {from: pokerReporter}); + // await tornRouter.pokeFromReporter('1', false, powerPokeOpts, {from: pokerReporter}); console.log('3 wrapper balance', fromEther(await torn.balanceOf(piTorn.address))); - const TORN_STAKING = '0x2fc93484614a34f26f7970cbb94615ba109bb4bf'; - const TORN_GOVERNANCE = '0x5efda50f22d34f262c29268506c5fa42cb56a1ce'; const governance = await ITornGovernance.at(TORN_GOVERNANCE); const staking = await ITornStaking.at(TORN_STAKING); console.log('lockedBalance', fromEther(await governance.lockedBalance(piTorn.address))); @@ -141,6 +103,11 @@ task('redeploy-torn-router', 'Redeploy TornRouter').setAction(async (__, {ethers console.log('lockedBalance', fromEther(await governance.lockedBalance(piTorn.address))); + await torn.approve(piTorn.address, amount, {from: tornHolder}); + await piTorn.deposit(amount, {from: tornHolder}); + + console.log('lockedBalance after deposit', fromEther(await governance.lockedBalance(piTorn.address))); + function getClaimParams(duration) { return tornConnector.packClaimParams(duration, GAS_TO_REINVEST); }