Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HP-25: C4 Audit Fixes, Dynamic Fee Changes #42

Merged
merged 21 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ typechain
#Hardhat files
cache
artifacts
.openzeppelin
.openzeppelin
57 changes: 43 additions & 14 deletions contracts/hyphen/LiquidityFarming.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ contract HyphenLiquidityFarming is
lpToken = _lpToken;
}

function setTrustedForwarder(address _tf) external onlyOwner {
_setTrustedForwarder(_tf);
}

/// @notice Initialize the rewarder pool.
/// @param _baseToken Base token to be used for the rewarder pool.
/// @param _rewardToken Reward token to be used for the rewarder pool.
Expand Down Expand Up @@ -183,6 +187,7 @@ contract HyphenLiquidityFarming is
/// @notice Sets the sushi per second to be distributed. Can only be called by the owner.
/// @param _rewardPerSecond The amount of Sushi to be distributed per second.
function setRewardPerSecond(address _baseToken, uint256 _rewardPerSecond) external onlyOwner {
require(_rewardPerSecond <= 10**40, "ERR__REWARD_PER_SECOND_TOO_HIGH");
rewardRateLog[_baseToken].push(RewardsPerSecondEntry(_rewardPerSecond, block.timestamp));
emit LogRewardPerSecond(_baseToken, _rewardPerSecond);
}
Expand Down Expand Up @@ -245,17 +250,35 @@ contract HyphenLiquidityFarming is
/// @param _nftId LP token nftId to withdraw.
/// @param _to The receiver of `amount` withdraw benefit.
function withdraw(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant {
address msgSender = _msgSender();
divyaN73 marked this conversation as resolved.
Show resolved Hide resolved
uint256 nftsStakedLength = nftIdsStaked[msgSender].length;
uint256 index = getStakedNftIndex(_msgSender(), _nftId);
_withdraw(_nftId, _to, index);
}

function getStakedNftIndex(address _staker, uint256 _nftId) public view returns (uint256) {
uint256 nftsStakedLength = nftIdsStaked[_staker].length;
uint256 index;
for (index = 0; index < nftsStakedLength; ++index) {
if (nftIdsStaked[msgSender][index] == _nftId) {
break;
unchecked {
for (index = 0; index < nftsStakedLength; ++index) {
if (nftIdsStaked[_staker][index] == _nftId) {
return index;
}
}
}
revert("ERR__NFT_NOT_STAKED");
}

function _withdraw(
uint256 _nftId,
address payable _to,
uint256 _index
) private {
address msgSender = _msgSender();

require(nftIdsStaked[msgSender][_index] == _nftId, "ERR__NOT_OWNER");
require(nftInfo[_nftId].staker == msgSender, "ERR__NOT_OWNER");
require(nftInfo[_nftId].unpaidRewards == 0, "ERR__UNPAID_REWARDS_EXIST");

require(index != nftsStakedLength, "ERR__NFT_NOT_STAKED");
nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1];
nftIdsStaked[msgSender][_index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1];
nftIdsStaked[msgSender].pop();

_sendRewardsForNft(_nftId, _to);
Expand All @@ -270,6 +293,14 @@ contract HyphenLiquidityFarming is
emit LogWithdraw(msgSender, baseToken, _nftId, _to);
}

function withdrawV2(
uint256 _nftId,
address payable _to,
uint256 _index
) external whenNotPaused nonReentrant {
_withdraw(_nftId, _to, _index);
}

/// @notice Extract all rewards without withdrawing LP tokens
/// @param _nftId LP token nftId for which rewards are to be withdrawn
/// @param _to The receiver of withdraw benefit.
Expand Down Expand Up @@ -332,14 +363,12 @@ contract HyphenLiquidityFarming is
/// @return pool Returns the pool that was updated.
function updatePool(address _baseToken) public whenNotPaused returns (PoolInfo memory pool) {
pool = poolInfo[_baseToken];
if (block.timestamp > pool.lastRewardTime) {
if (totalSharesStaked[_baseToken] > 0) {
pool.accTokenPerShare = getUpdatedAccTokenPerShare(_baseToken);
}
pool.lastRewardTime = block.timestamp;
poolInfo[_baseToken] = pool;
emit LogUpdatePool(_baseToken, pool.lastRewardTime, totalSharesStaked[_baseToken], pool.accTokenPerShare);
if (totalSharesStaked[_baseToken] > 0) {
pool.accTokenPerShare = getUpdatedAccTokenPerShare(_baseToken);
}
pool.lastRewardTime = block.timestamp;
poolInfo[_baseToken] = pool;
emit LogUpdatePool(_baseToken, pool.lastRewardTime, totalSharesStaked[_baseToken], pool.accTokenPerShare);
}

/// @notice View function to see the tokens staked by a given user.
Expand Down
113 changes: 106 additions & 7 deletions contracts/hyphen/LiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ contract LiquidityPool is
string tag
);
event GasFeeWithdraw(address indexed tokenAddress, address indexed owner, uint256 indexed amount);
event TrustedForwarderChanged(address indexed forwarderAddress);
event LiquidityProvidersChanged(address indexed liquidityProvidersAddress);
event TokenManagerChanged(address indexed tokenManagerAddress);
event BaseGasUpdated(uint256 indexed baseGas);
divyaN73 marked this conversation as resolved.
Show resolved Hide resolved
event EthReceived(address, uint256);

// MODIFIERS
Expand Down Expand Up @@ -127,9 +127,7 @@ contract LiquidityPool is
}

function setTrustedForwarder(address trustedForwarder) external onlyOwner {
require(trustedForwarder != address(0), "TrustedForwarder can't be 0");
_trustedForwarder = trustedForwarder;
emit TrustedForwarderChanged(trustedForwarder);
_setTrustedForwarder(trustedForwarder);
}

function setLiquidityProviders(address _liquidityProviders) external onlyOwner {
Expand All @@ -146,6 +144,7 @@ contract LiquidityPool is

function setBaseGas(uint128 gas) external onlyOwner {
baseGas = gas;
emit BaseGasUpdated(baseGas);
}

function getExecutorManager() external view returns (address) {
Expand Down Expand Up @@ -181,6 +180,8 @@ contract LiquidityPool is
uint256 amount,
string calldata tag
) public tokenChecks(tokenAddress) whenNotPaused nonReentrant {
require(toChainId != block.chainid, "To chain must be different than current chain");
require(tokenAddress != NATIVE, "wrong function");
TokenConfig memory config = tokenManager.getDepositConfig(toChainId, tokenAddress);

require(config.min <= amount && config.max >= amount, "Deposit amount not in Cap limit");
Expand Down Expand Up @@ -270,6 +271,7 @@ contract LiquidityPool is
uint256 toChainId,
string calldata tag
) external payable whenNotPaused nonReentrant {
require(toChainId != block.chainid, "To chain must be different than current chain");
require(
tokenManager.getDepositConfig(toChainId, NATIVE).min <= msg.value &&
tokenManager.getDepositConfig(toChainId, NATIVE).max >= msg.value,
Expand Down Expand Up @@ -369,28 +371,125 @@ contract LiquidityPool is
return [amountToTransfer, lpFee, transferFeeAmount, gasFee];
}

function sendFundsToUserV2(
address tokenAddress,
uint256 amount,
address payable receiver,
bytes calldata depositHash,
uint256 nativeTokenPriceInTransferredToken,
uint256 fromChainId
) external nonReentrant onlyExecutor whenNotPaused {
uint256 initialGas = gasleft();
TokenConfig memory config = tokenManager.getTransferConfig(tokenAddress);
require(config.min <= amount && config.max >= amount, "Withdraw amount not in Cap limit");
require(receiver != address(0), "Bad receiver address");

(bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash);

require(!status, "Already Processed");
processedHash[hashSendTransaction] = true;

// uint256 amountToTransfer, uint256 lpFee, uint256 transferFeeAmount, uint256 gasFee
uint256[4] memory transferDetails = getAmountToTransferV2(
initialGas,
tokenAddress,
amount,
nativeTokenPriceInTransferredToken
);

liquidityProviders.decreaseCurrentLiquidity(tokenAddress, transferDetails[0]);

if (tokenAddress == NATIVE) {
(bool success, ) = receiver.call{value: transferDetails[0]}("");
require(success, "Native Transfer Failed");
} else {
SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, transferDetails[0]);
}

emit AssetSent(
tokenAddress,
amount,
transferDetails[0],
receiver,
depositHash,
fromChainId,
transferDetails[1],
transferDetails[2],
transferDetails[3]
);
}

/**
* @dev Internal function to calculate amount of token that needs to be transfered afetr deducting all required fees.
* Fee to be deducted includes gas fee, lp fee and incentive pool amount if needed.
* @param initialGas Gas provided initially before any calculations began
* @param tokenAddress Token address for which calculation needs to be done
* @param amount Amount of token to be transfered before deducting the fee
* @param nativeTokenPriceInTransferredToken Price of native token in terms of the token being transferred (multiplied base div), used to calculate gas fee
* @return [ amountToTransfer, lpFee, transferFeeAmount, gasFee ]
*/
function getAmountToTransferV2(
uint256 initialGas,
address tokenAddress,
uint256 amount,
uint256 nativeTokenPriceInTransferredToken
) internal returns (uint256[4] memory) {
TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress);
uint256 transferFeePerc = _getTransferFee(tokenAddress, amount, tokenInfo);
uint256 lpFee;
if (transferFeePerc > tokenInfo.equilibriumFee) {
// Here add some fee to incentive pool also
lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR;
unchecked {
incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenInfo.equilibriumFee)) / BASE_DIVISOR;
}
} else {
lpFee = (amount * transferFeePerc) / BASE_DIVISOR;
}
uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR;

liquidityProviders.addLPFee(tokenAddress, lpFee);

uint256 totalGasUsed = initialGas + tokenInfo.transferOverhead + baseGas - gasleft();
uint256 gasFee = (totalGasUsed * nativeTokenPriceInTransferredToken * tx.gasprice) / BASE_DIVISOR;

gasFeeAccumulatedByToken[tokenAddress] += gasFee;
gasFeeAccumulated[tokenAddress][_msgSender()] += gasFee;
uint256 amountToTransfer = amount - (transferFeeAmount + gasFee);
return [amountToTransfer, lpFee, transferFeeAmount, gasFee];
}

function _getTransferFee(
address tokenAddress,
uint256 amount,
TokenInfo memory tokenInfo
) private view returns (uint256 fee) {
) private view returns (uint256) {
uint256 currentLiquidity = getCurrentLiquidity(tokenAddress);
uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress);

uint256 resultingLiquidity = currentLiquidity - amount;

// We return a constant value in excess state
if (resultingLiquidity > providedLiquidity) {
return tokenManager.excessStateTransferFeePerc(tokenAddress);
}

// Fee is represented in basis points * 10 for better accuracy
uint256 numerator = providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e)
uint256 numerator = providedLiquidity * providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e) ^ 2
uint256 denominator = tokenInfo.equilibriumFee *
providedLiquidity *
providedLiquidity +
(tokenInfo.maxFee - tokenInfo.equilibriumFee) *
resultingLiquidity; // F(e) * L(e) + (F(max) - F(e)) * L(r)
resultingLiquidity *
resultingLiquidity; // F(e) * L(e) ^ 2 + (F(max) - F(e)) * L(r) ^ 2

uint256 fee;
if (denominator == 0) {
fee = 0;
} else {
fee = numerator / denominator;
}
return fee;
}

function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256) {
Expand Down
32 changes: 19 additions & 13 deletions contracts/hyphen/LiquidityProviders.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\
// $$ | \__| \__| $$ |\__| $$ | $$ __$$\ \__| $$ |
// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$ | $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$$$$$$\
// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\
// $$ | \__| \__| $$ |\__| $$ | $$ __$$\ \__| $$ |
// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$ | $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$$$$$$\
// $$ | $$ |$$ __$$\ $$ | $$ |$$ |$$ __$$ |$$ |\_$$ _| $$ | $$ | $$$$$$$ |$$ __$$\ $$ __$$\\$$\ $$ |$$ |$$ __$$ |$$ __$$\ $$ __$$\ $$ _____|
// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ ____/ $$ | \__|$$ / $$ |\$$\$$ / $$ |$$ / $$ |$$$$$$$$ |$$ | \__|\$$$$$$\
// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ | $$ | $$ | \$$$ / $$ |$$ | $$ |$$ ____|$$ | \____$$\
// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ ____/ $$ | \__|$$ / $$ |\$$\$$ / $$ |$$ / $$ |$$$$$$$$ |$$ | \__|\$$$$$$\
// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ | $$ | $$ | \$$$ / $$ |$$ | $$ |$$ ____|$$ | \____$$\
// $$$$$$$$\ $$ |\$$$$$$$ |\$$$$$$ |$$ |\$$$$$$$ |$$ | \$$$$ |\$$$$$$$ | $$ | $$ | \$$$$$$ | \$ / $$ |\$$$$$$$ |\$$$$$$$\ $$ | $$$$$$$ |
// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \__| \______/ \_/ \__| \_______| \_______|\__| \_______/
// $$ | $$\ $$ |
// $$ | \$$$$$$ |
// \__| \______/
// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \__| \______/ \_/ \__| \_______| \_______|\__| \_______/
// $$ | $$\ $$ |
// $$ | \$$$$$$ |
// \__| \______/
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;

Expand Down Expand Up @@ -167,6 +167,10 @@ contract LiquidityProviders is
tokenManager = ITokenManager(_tokenManager);
}

function setTrustedForwarder(address _tf) external onlyOwner {
_setTrustedForwarder(_tf);
}

/**
* @dev To be called post initialization, used to set address of WhiteListPeriodManager Contract
* @param _whiteListPeriodManager address of WhiteListPeriodManager
Expand All @@ -189,8 +193,8 @@ contract LiquidityProviders is
* @return Price of Base token in terms of LP Shares
*/
function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) {
uint256 supply = totalSharesMinted[_baseToken];
if (supply > 0) {
uint256 reserve = totalReserve[_baseToken];
if (reserve > 0) {
return totalSharesMinted[_baseToken] / totalReserve[_baseToken];
}
return BASE_DIVISOR;
Expand Down Expand Up @@ -259,6 +263,7 @@ contract LiquidityProviders is
* record in the newly minted NFT
*/
function addNativeLiquidity() external payable nonReentrant tokenChecks(NATIVE) whenNotPaused {
require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET");
(bool success, ) = address(liquidityPool).call{value: msg.value}("");
require(success, "ERR__NATIVE_TRANSFER_FAILED");
_addLiquidity(NATIVE, msg.value);
Expand Down Expand Up @@ -296,7 +301,7 @@ contract LiquidityProviders is

uint256 mintedSharesAmount;
// Adding liquidity in the pool for the first time
if (totalReserve[token] == 0) {
if (totalReserve[token] == 0 || totalSharesMinted[token] == 0) {
mintedSharesAmount = BASE_DIVISOR * _amount;
} else {
mintedSharesAmount = (_amount * totalSharesMinted[token]) / totalReserve[token];
Expand Down Expand Up @@ -344,6 +349,7 @@ contract LiquidityProviders is
(address token, , ) = lpToken.tokenMetadata(_nftId);
require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED");
require(token == NATIVE, "ERR__WRONG_FUNCTION");
require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET");
(bool success, ) = address(liquidityPool).call{value: msg.value}("");
require(success, "ERR__NATIVE_TRANSFER_FAILED");
_increaseLiquidity(_nftId, msg.value);
Expand All @@ -365,7 +371,7 @@ contract LiquidityProviders is
require(_amount != 0, "ERR__INVALID_AMOUNT");
require(nftSuppliedLiquidity >= _amount, "ERR__INSUFFICIENT_LIQUIDITY");
whiteListPeriodManager.beforeLiquidityRemoval(_msgSender(), _tokenAddress, _amount);
// Claculate how much shares represent input amount
// Calculate how much shares represent input amount
uint256 lpSharesForInputAmount = _amount * getTokenPriceInLPShares(_tokenAddress);

// Calculate rewards accumulated
Expand Down
4 changes: 4 additions & 0 deletions contracts/hyphen/interfaces/ITokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ interface ITokenManager {
TokenConfig memory config
);

function excessStateTransferFeePerc(address tokenAddress) external view returns (uint256);

function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory);

function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory);

function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory);

function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) external;
}
Loading