Skip to content

Commit

Permalink
Merge pull request #12 from powerpool-finance/torn-connector-audit-fixes
Browse files Browse the repository at this point in the history
add piTokenCallback to PowerIndexRouter.sol
defi-lead authored Aug 5, 2022
2 parents fd46a61 + b35977d commit d7104f8
Showing 10 changed files with 469 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
@@ -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],
75 changes: 54 additions & 21 deletions contracts/PowerIndexRouter.sol
Original file line number Diff line number Diff line change
@@ -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%
@@ -95,7 +95,9 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
uint256 minInterval;
uint256 maxInterval;
uint256 piTokenUnderlyingBalance;
uint256 subFromExpectedStakeAmount;
bool atLeastOneForceRebalance;
bool skipCanPokeCheck;
}

modifier onlyEOA() {
@@ -294,6 +296,11 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
require(success, string(result));
}

function piTokenCallback(address, uint256 _withdrawAmount) external payable virtual override {
PokeFromState memory state = PokeFromState(0, 0, 0, _withdrawAmount, false, true);
_rebalance(state, false, false);
}

/**
* @notice Call poke by Reporter.
* @param _reporterId Reporter ID.
@@ -360,13 +367,35 @@ 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, 0, false, false);
(state.minInterval, state.maxInterval) = _getMinMaxReportInterval();

state.piTokenUnderlyingBalance = piToken.getUnderlyingBalance();
(uint256[] memory stakedBalanceList, uint256 totalStakedBalance) = _getUnderlyingStakedList();
_rebalance(state, _claimAndDistributeRewards, _isSlasher);

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 {
if (connectors.length == 1 && reserveRatio == 0 && !_claimAndDistributeRewards) {
if (s.subFromExpectedStakeAmount > 0) {
_rebalancePoke(connectors[0], StakeStatus.EXCESS, s.subFromExpectedStakeAmount);
} else {
_rebalancePoke(connectors[0], StakeStatus.SHORTAGE, piToken.getUnderlyingBalance());
}
return;
}

state.atLeastOneForceRebalance = false;
s.piTokenUnderlyingBalance = piToken.getUnderlyingBalance();
(uint256[] memory stakedBalanceList, uint256 totalStakedBalance) = _getUnderlyingStakedList();

RebalanceConfig[] memory configs = new RebalanceConfig[](connectors.length);

@@ -377,19 +406,20 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
}

(StakeStatus status, uint256 diff, bool shouldClaim, bool forceRebalance) = getStakeAndClaimStatus(
state.piTokenUnderlyingBalance,
s.piTokenUnderlyingBalance,
totalStakedBalance,
stakedBalanceList[i],
s.subFromExpectedStakeAmount,
_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 +428,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 +454,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
}
return
_isSlasher
? (lastRebalancedAt + _maxInterval < block.timestamp)
: (lastRebalancedAt + _minInterval < block.timestamp);
? (lastRebalancedByPokerAt + _maxInterval < block.timestamp)
: (lastRebalancedByPokerAt + _minInterval < block.timestamp);
}

/**
@@ -564,13 +587,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 _subFromExpectedStakeAmount,
bool _claimAndDistributeRewards,
Connector memory _c
)
@@ -587,6 +611,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
_leftOnPiTokenBalance,
_totalStakedBalance,
_stakedBalance,
_subFromExpectedStakeAmount,
_c.share
);
shouldClaim = _claimAndDistributeRewards && claimRewardsIntervalReached(_c.lastClaimRewardsAt);
@@ -606,6 +631,7 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
uint256 _leftOnPiTokenBalance,
uint256 _totalStakedBalance,
uint256 _stakedBalance,
uint256 _subFromExpectedStakeAmount,
uint256 _share
)
public
@@ -622,7 +648,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
_leftOnPiTokenBalance,
_totalStakedBalance,
_stakedBalance,
_share
_share,
_subFromExpectedStakeAmount
);

if (status == StakeStatus.EQUILIBRIUM) {
@@ -754,7 +781,8 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
uint256 _leftOnPiToken,
uint256 _totalStakedBalance,
uint256 _stakedBalance,
uint256 _share
uint256 _share,
uint256 _subFromExpectedStakeAmount
)
public
view
@@ -767,6 +795,11 @@ contract PowerIndexRouter is PowerIndexRouterInterface, PowerIndexNaiveRouter {
require(_reserveRatioPct <= HUNDRED_PCT, "RR_GREATER_THAN_100_PCT");
expectedStakeAmount = getExpectedStakeAmount(_reserveRatioPct, _leftOnPiToken, _totalStakedBalance, _share);

uint256 subFromExpectedStakeAmountByShare = _subFromExpectedStakeAmount.mul(_share).div(1 ether);
if (expectedStakeAmount >= subFromExpectedStakeAmountByShare) {
expectedStakeAmount -= subFromExpectedStakeAmountByShare;
}

if (expectedStakeAmount > _stakedBalance) {
status = StakeStatus.SHORTAGE;
diff = expectedStakeAmount.sub(_stakedBalance);
5 changes: 4 additions & 1 deletion contracts/WrappedPiErc20.sol
Original file line number Diff line number Diff line change
@@ -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);
1 change: 0 additions & 1 deletion contracts/connectors/TornPowerIndexConnector.sol
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -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();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 && 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 .",
19 changes: 10 additions & 9 deletions tasks/deployTornVault.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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');
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 VestedLpMining').setAction(async (__, {ethers,
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 VestedLpMining').setAction(async (__, {ethers,
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());
Loading

0 comments on commit d7104f8

Please sign in to comment.