Skip to content

Commit df278ad

Browse files
committed
fixed critical bug inside fee manager
1 parent 0104c47 commit df278ad

File tree

10 files changed

+1148
-992
lines changed

10 files changed

+1148
-992
lines changed

.mocharc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"require": "hardhat/register",
3+
"timeout": 40000,
4+
"_": [
5+
"test/**/*.ts"
6+
]
7+
}

contracts/BaseDynamicFeeManager.sol

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ abstract contract BaseDynamicFeeManager is
5555
// Max. amount for fee entries
5656
uint256 public constant MAX_FEE_AMOUNT = 30;
5757

58+
// Min. amount for swap / liquify
59+
uint256 public constant MIN_SWAP_OR_LIQUIFY_AMOUNT = 1 ether;
60+
5861
// Fee divider
5962
uint256 internal constant FEE_DIVIDER = 100_000;
6063

@@ -65,11 +68,11 @@ abstract contract BaseDynamicFeeManager is
6568
// List of all currently added fees
6669
FeeEntry[] internal feeEntries;
6770

68-
// Mapping id to current liquify or swap amounts
71+
// Mapping id to current swap or liquify amounts
6972
mapping(bytes32 => uint256) internal feeEntryAmounts;
7073

7174
// Fees enabled state
72-
bool private _feesEnabled = false;
75+
bool internal feesEnabled_ = false;
7376

7477
// Pancake Router address
7578
IPancakeRouter02 private _pancakeRouter =
@@ -134,7 +137,7 @@ abstract contract BaseDynamicFeeManager is
134137
}
135138

136139
function setFeesEnabled(bool value) external override onlyRole(ADMIN) {
137-
_feesEnabled = value;
140+
feesEnabled_ = value;
138141

139142
emit FeeEnabledUpdated(value);
140143
}
@@ -251,7 +254,7 @@ abstract contract BaseDynamicFeeManager is
251254
}
252255

253256
function feesEnabled() public view override returns (bool) {
254-
return _feesEnabled;
257+
return feesEnabled_;
255258
}
256259

257260
function pancakeRouter()
@@ -472,17 +475,24 @@ abstract contract BaseDynamicFeeManager is
472475
/**
473476
* Returns the amount used for swap / liquify based on volume percentage for swap / liquify
474477
*
478+
* @param feeId bytes32 - Fee entry id
475479
* @param swapOrLiquifyAmount uint256 - Fee entry swap or liquify amount
476480
* @param percentageVolume uint256 - Volume percentage for swap / liquify
477481
* @param pancakePairAddress address - Pancakeswap pair address to use for volume
478482
*
479483
* @return amount uint256 - Amount used for swap / liquify
480484
*/
481485
function _getSwapOrLiquifyAmount(
486+
bytes32 feeId,
482487
uint256 swapOrLiquifyAmount,
483488
uint256 percentageVolume,
484489
address pancakePairAddress
485490
) internal view returns (uint256 amount) {
491+
// If no percentage and fixed amount is set, use balance
492+
if (percentageVolume == 0 && swapOrLiquifyAmount == 0) {
493+
return feeEntryAmounts[feeId];
494+
}
495+
486496
if (pancakePairAddress == address(0) || percentageVolume == 0) {
487497
return swapOrLiquifyAmount;
488498
}
@@ -495,6 +505,16 @@ abstract contract BaseDynamicFeeManager is
495505
uint256 percentualAmount = (pancakePairTokenBalance *
496506
percentageVolume) / 100;
497507

508+
// If swap or liquify amount is zero, and percentual amount is
509+
// higher than collected amount, return collected amount, otherwise
510+
// return percentual amount
511+
if (swapOrLiquifyAmount == 0) {
512+
return
513+
percentualAmount > feeEntryAmounts[feeId]
514+
? feeEntryAmounts[feeId]
515+
: percentualAmount;
516+
}
517+
498518
// Do not exceed swap or liquify amount from fee entry
499519
if (percentualAmount >= swapOrLiquifyAmount) {
500520
return swapOrLiquifyAmount;

contracts/DynamicFeeManager.sol

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ contract DynamicFeeManager is BaseDynamicFeeManager {
159159
* @param to address - Receiver address
160160
* @param tFee uint256 - Fee amount
161161
* @param fee FeeEntry - Fee Entry
162+
* @param bypassSwapAndLiquify bool - Indicator, if swap and liquify should be bypassed
162163
*/
163164
function _reflectFee(
164165
address from,
@@ -192,24 +193,33 @@ contract DynamicFeeManager is BaseDynamicFeeManager {
192193
// Check if swap / liquify amount was reached
193194
if (
194195
!bypassSwapAndLiquify &&
196+
feeEntryAmounts[fee.id] >= MIN_SWAP_OR_LIQUIFY_AMOUNT &&
195197
feeEntryAmounts[fee.id] >= fee.swapOrLiquifyAmount
196198
) {
199+
// Disable fees, to prevent PancakeSwap pair recursive calls
200+
feesEnabled_ = false;
201+
202+
// Check if swap / liquify amount was reached
197203
uint256 tokenSwapped = 0;
198204

199-
if (fee.doSwapForBusd) {
205+
if (fee.doSwapForBusd && from != pancakePairBusdAddress()) {
200206
// Calculate amount of token we're going to swap
201207
tokenSwapped = _getSwapOrLiquifyAmount(
208+
fee.id,
202209
fee.swapOrLiquifyAmount,
203210
percentageVolumeSwap(),
204211
pancakePairBusdAddress()
205212
);
206213

207214
// Swap token for BUSD
208215
_swapTokensForBusd(tokenSwapped, fee.destination);
209-
} else if (fee.doLiquify) {
216+
}
217+
218+
if (fee.doLiquify) {
210219
// Swap (BNB) and liquify token
211220
tokenSwapped = _swapAndLiquify(
212221
_getSwapOrLiquifyAmount(
222+
fee.id,
213223
fee.swapOrLiquifyAmount,
214224
percentageVolumeLiquify(),
215225
pancakePairBnbAddress()
@@ -220,6 +230,9 @@ contract DynamicFeeManager is BaseDynamicFeeManager {
220230

221231
// Subtract amount of swapped token from fee entry amount
222232
feeEntryAmounts[fee.id] = feeEntryAmounts[fee.id] - tokenSwapped;
233+
234+
// Enable fees again
235+
feesEnabled_ = true;
223236
}
224237

225238
// Check if callback should be called on destination
@@ -258,11 +271,9 @@ contract DynamicFeeManager is BaseDynamicFeeManager {
258271
*
259272
* @return isValid bool - Indicates, if the fee entry is still valid
260273
*/
261-
function _isFeeEntryValid(FeeEntry memory fee)
262-
private
263-
view
264-
returns (bool isValid)
265-
{
274+
function _isFeeEntryValid(
275+
FeeEntry memory fee
276+
) private view returns (bool isValid) {
266277
return fee.expiresAt == 0 || block.timestamp <= fee.expiresAt;
267278
}
268279

@@ -302,11 +313,10 @@ contract DynamicFeeManager is BaseDynamicFeeManager {
302313
*
303314
* @return tFee - Total Fee Amount
304315
*/
305-
function _calculateFee(uint256 amount, uint256 percentage)
306-
private
307-
pure
308-
returns (uint256 tFee)
309-
{
316+
function _calculateFee(
317+
uint256 amount,
318+
uint256 percentage
319+
) private pure returns (uint256 tFee) {
310320
return (amount * percentage) / FEE_DIVIDER;
311321
}
312322

contracts/mocks/MockERC20.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ import "@openzeppelin/contracts/access/Ownable.sol";
88

99
contract MockERC20 is ERC20, Ownable {
1010
constructor() ERC20("MockERC20", "MERC20") {
11-
_mint(_msgSender(), 100 ether);
11+
_mint(_msgSender(), 100_000_000 ether);
1212
}
1313
}

contracts/mocks/MockPancakeRouter.sol

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,27 @@ contract MockPancakeRouter {
99
event MockEvent(uint256 value);
1010

1111
address private immutable _weth;
12-
address private immutable _pair;
1312

14-
constructor(address weth, address pair) {
13+
// See https://github.com/pancakeswap/pancake-smart-contracts/blob/master/projects/exchange-protocol/contracts/PancakeFactory.sol#L13
14+
mapping(address => mapping(address => address)) public getPair;
15+
16+
constructor(
17+
address weth,
18+
address busd,
19+
address wsi,
20+
address wethPair,
21+
address busdPair
22+
) {
23+
// BNB
1524
_weth = weth;
16-
_pair = pair;
25+
26+
// BNB <-> WSI
27+
getPair[weth][wsi] = wethPair;
28+
getPair[wsi][weth] = wethPair;
29+
30+
// BUSD <-> WSI
31+
getPair[busd][wsi] = busdPair;
32+
getPair[wsi][busd] = busdPair;
1733
}
1834

1935
function WETH() public view returns (address) {
@@ -36,7 +52,9 @@ contract MockPancakeRouter {
3652
uint256 liquidity
3753
)
3854
{
39-
IERC20(token).transferFrom(msg.sender, _pair, amountTokenDesired);
55+
address pair = getPair[_weth][token];
56+
57+
IERC20(token).transferFrom(msg.sender, pair, amountTokenDesired);
4058

4159
return (amountTokenDesired, msg.value, 0);
4260
}
@@ -48,7 +66,13 @@ contract MockPancakeRouter {
4866
address to,
4967
uint256 deadline
5068
) public {
51-
IERC20(path[0]).transferFrom(msg.sender, _pair, amountIn);
69+
require(amountIn > 0, "MockPancakeRouter: Invalid input amount");
70+
71+
address pair = getPair[path[0]][path[1]];
72+
73+
IERC20(path[0]).transferFrom(msg.sender, pair, amountIn);
74+
// MockPancakePair(_pair).swap(path[1], address(0), amountIn);
75+
payable(to).transfer(amountIn);
5276
}
5377

5478
function swapExactETHForTokensSupportingFeeOnTransferTokens(
@@ -57,7 +81,10 @@ contract MockPancakeRouter {
5781
address to,
5882
uint256 deadline
5983
) public payable {
60-
MockPancakePair(_pair).swap(path[1], msg.sender, amountOutMin);
84+
address pair = getPair[path[0]][path[1]];
85+
86+
IERC20(path[0]).transfer(pair, msg.value);
87+
MockPancakePair(pair).swap(path[1], to, amountOutMin);
6188
}
6289

6390
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
@@ -67,6 +94,13 @@ contract MockPancakeRouter {
6794
address to,
6895
uint256 deadline
6996
) public {
70-
IERC20(path[0]).transferFrom(msg.sender, _pair, amountIn);
97+
address pair = getPair[path[0]][path[1]];
98+
99+
IERC20(path[0]).transferFrom(msg.sender, pair, amountIn);
100+
MockPancakePair(pair).swap(
101+
path[1],
102+
to,
103+
amountOutMin > 0 ? amountOutMin : amountIn
104+
);
71105
}
72106
}

hardhat.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ const config: HardhatUserConfig = {
4545
accounts:
4646
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
4747
},
48+
hardhat: {
49+
forking: {
50+
url: process.env.BSC_GETBLOCK_URL || '',
51+
}
52+
}
4853
},
4954
gasReporter: {
5055
enabled: process.env.REPORT_GAS !== undefined,

0 commit comments

Comments
 (0)