diff --git a/pkg/interfaces/contracts/pool-weighted/WeightedPoolUserData.sol b/pkg/interfaces/contracts/pool-weighted/WeightedPoolUserData.sol index e192e5a7c1..3d057aba77 100644 --- a/pkg/interfaces/contracts/pool-weighted/WeightedPoolUserData.sol +++ b/pkg/interfaces/contracts/pool-weighted/WeightedPoolUserData.sol @@ -68,4 +68,37 @@ library WeightedPoolUserData { { (, amountsOut, maxBPTAmountIn) = abi.decode(self, (ExitKind, uint256[], uint256)); } + + // function related to custom fee + function tokenInForExactBptOutCustomFee(bytes memory self) internal pure returns (uint256 customFee) { + (, , , customFee) = abi.decode(self, (JoinKind, uint256, uint256, uint256)); + } + + function exactTokensInForBptOutCustomFee(bytes memory self) + internal + pure + returns (uint256 customFee) + { + (, , , customFee) = abi.decode(self, (JoinKind, uint256[], uint256, uint256)); + } + + function exactBptInForTokenOutCustomFee(bytes memory self) internal pure returns (uint256 customFee) { + (, , , customFee) = abi.decode(self, (ExitKind, uint256, uint256, uint256)); + } + + function bptInForExactTokensOutCustomFee(bytes memory self) + internal + pure + returns (uint256 customFee) + { + (, , , customFee) = abi.decode(self, (ExitKind, uint256[], uint256, uint256)); + } + + function swapCustomFee(bytes memory self) + internal + pure + returns (uint256 customFee) + { + (customFee) = abi.decode(self, (uint256)); + } } diff --git a/pkg/interfaces/contracts/vault/IBasePool.sol b/pkg/interfaces/contracts/vault/IBasePool.sol index 4411e51d10..22ec9a4f5b 100644 --- a/pkg/interfaces/contracts/vault/IBasePool.sol +++ b/pkg/interfaces/contracts/vault/IBasePool.sol @@ -45,6 +45,9 @@ interface IBasePool is IPoolSwapStructs { * Contracts implementing this function should check that the caller is indeed the Vault before performing any * state-changing operations, such as minting pool shares. */ + // enum for transaction type + enum OperationType { JOIN, EXIT, SWAP } + function onJoinPool( bytes32 poolId, address sender, diff --git a/pkg/pool-utils/contracts/BaseGeneralPool.sol b/pkg/pool-utils/contracts/BaseGeneralPool.sol index bfbd43a3c1..79d24f51dd 100644 --- a/pkg/pool-utils/contracts/BaseGeneralPool.sol +++ b/pkg/pool-utils/contracts/BaseGeneralPool.sol @@ -54,7 +54,7 @@ abstract contract BaseGeneralPool is IGeneralPool, BasePool { uint256[] memory scalingFactors ) internal virtual returns (uint256) { // Fees are subtracted before scaling, to reduce the complexity of the rounding direction analysis. - swapRequest.amount = _subtractSwapFeeAmount(swapRequest.amount); + swapRequest.amount = _subtractSwapFeeAmount(swapRequest.amount, getSwapFeePercentage()); _upscaleArray(balances, scalingFactors); swapRequest.amount = _upscale(swapRequest.amount, scalingFactors[indexIn]); @@ -81,7 +81,7 @@ abstract contract BaseGeneralPool is IGeneralPool, BasePool { amountIn = _downscaleUp(amountIn, scalingFactors[indexIn]); // Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis. - return _addSwapFeeAmount(amountIn); + return _addSwapFeeAmount(amountIn, getSwapFeePercentage()); } /* diff --git a/pkg/pool-utils/contracts/BaseMinimalSwapInfoPool.sol b/pkg/pool-utils/contracts/BaseMinimalSwapInfoPool.sol index 7828e5eae0..86ab5a4e3f 100644 --- a/pkg/pool-utils/contracts/BaseMinimalSwapInfoPool.sol +++ b/pkg/pool-utils/contracts/BaseMinimalSwapInfoPool.sol @@ -35,6 +35,8 @@ abstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool { uint256 balanceTokenOut ) public override onlyVault(request.poolId) returns (uint256) { _beforeSwapJoinExit(); + uint256 _fee = getSwapFeePercentage(request.userData, OperationType.SWAP); + emit SwapFeePercentageChanged(_fee); uint256 scalingFactorTokenIn = _scalingFactor(request.tokenIn); uint256 scalingFactorTokenOut = _scalingFactor(request.tokenOut); @@ -44,7 +46,7 @@ abstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool { if (request.kind == IVault.SwapKind.GIVEN_IN) { // Fees are subtracted before scaling, to reduce the complexity of the rounding direction analysis. - request.amount = _subtractSwapFeeAmount(request.amount); + request.amount = _subtractSwapFeeAmount(request.amount, _fee); // All token amounts are upscaled. request.amount = _upscale(request.amount, scalingFactorTokenIn); @@ -63,7 +65,7 @@ abstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool { amountIn = _downscaleUp(amountIn, scalingFactorTokenIn); // Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis. - return _addSwapFeeAmount(amountIn); + return _addSwapFeeAmount(amountIn, _fee); } } @@ -99,4 +101,4 @@ abstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool { uint256 balanceTokenIn, uint256 balanceTokenOut ) internal virtual returns (uint256); -} +} \ No newline at end of file diff --git a/pkg/pool-utils/contracts/BasePool.sol b/pkg/pool-utils/contracts/BasePool.sol index 58f57d0ce2..df16ce2e21 100644 --- a/pkg/pool-utils/contracts/BasePool.sol +++ b/pkg/pool-utils/contracts/BasePool.sol @@ -173,6 +173,13 @@ abstract contract BasePool is return _miscData.decodeUint(_SWAP_FEE_PERCENTAGE_OFFSET, _SWAP_FEE_PERCENTAGE_BIT_LENGTH); } + // overloaded method implementation + function getSwapFeePercentage(bytes memory , + OperationType ) public view virtual returns (uint256) { + // override the function as per the need in the derived classes + return getSwapFeePercentage(); + } + /** * @notice Return the ProtocolFeesCollector contract. * @dev This is immutable, and retrieved from the Vault on construction. (It is also immutable in the Vault.) @@ -592,17 +599,18 @@ abstract contract BasePool is /** * @dev Adds swap fee amount to `amount`, returning a higher value. */ - function _addSwapFeeAmount(uint256 amount) internal view returns (uint256) { + function _addSwapFeeAmount(uint256 amount,uint256 _fee) internal pure returns (uint256) { // This returns amount + fee amount, so we round up (favoring a higher fee amount). - return amount.divUp(getSwapFeePercentage().complement()); + return amount.divUp(_fee.complement()); } + /** * @dev Subtracts swap fee amount from `amount`, returning a lower value. */ - function _subtractSwapFeeAmount(uint256 amount) internal view returns (uint256) { + function _subtractSwapFeeAmount(uint256 amount, uint256 _fee) internal pure returns (uint256) { // This returns amount - fee amount, so we round up (favoring a higher fee amount). - uint256 feeAmount = amount.mulUp(getSwapFeePercentage()); + uint256 feeAmount = amount.mulUp(_fee); return amount.sub(feeAmount); } @@ -784,4 +792,4 @@ abstract contract BasePool is } } } -} +} \ No newline at end of file diff --git a/pkg/pool-weighted/contracts/BaseWeightedPool.sol b/pkg/pool-weighted/contracts/BaseWeightedPool.sol index 718ec756bb..19bd96d1b8 100644 --- a/pkg/pool-weighted/contracts/BaseWeightedPool.sol +++ b/pkg/pool-weighted/contracts/BaseWeightedPool.sol @@ -225,6 +225,15 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { userData ); + // _doJoin performs actions specific to type of join + // but it's a view function so can not emit event + + WeightedPoolUserData.JoinKind kind = userData.joinKind(); + if(kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT || + kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT){ + emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.JOIN)); + } + _afterJoinExit( preJoinExitInvariant, balances, @@ -280,7 +289,7 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { normalizedWeights, amountsIn, totalSupply, - getSwapFeePercentage() + getSwapFeePercentage(userData, OperationType.JOIN) ); _require(bptAmountOut >= minBPTAmountOut, Errors.BPT_OUT_MIN_AMOUNT); @@ -304,7 +313,7 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { normalizedWeights[tokenIndex], bptAmountOut, totalSupply, - getSwapFeePercentage() + getSwapFeePercentage(userData, OperationType.JOIN) ); // We join in a single token, so we initialize amountsIn with zeros @@ -353,6 +362,15 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { userData ); + // _doExit performs actions specific to type of exit + // but it's a view function so can not emit event + + WeightedPoolUserData.ExitKind kind = userData.exitKind(); + if(kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT || + kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT){ + emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.EXIT)); + } + _afterJoinExit( preJoinExitInvariant, balances, @@ -407,7 +425,7 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { normalizedWeights[tokenIndex], bptAmountIn, totalSupply, - getSwapFeePercentage() + getSwapFeePercentage(userData, OperationType.EXIT) ); // This is an exceptional situation in which the fee is charged on a token out instead of a token in. @@ -448,7 +466,7 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool { normalizedWeights, amountsOut, totalSupply, - getSwapFeePercentage() + getSwapFeePercentage(userData, OperationType.EXIT) ); _require(bptAmountIn <= maxBPTAmountIn, Errors.BPT_IN_MAX_AMOUNT); diff --git a/pkg/pool-weighted/contracts/CustomFeeAuthorizer.sol b/pkg/pool-weighted/contracts/CustomFeeAuthorizer.sol new file mode 100644 index 0000000000..45a5d6b7c7 --- /dev/null +++ b/pkg/pool-weighted/contracts/CustomFeeAuthorizer.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + + +contract CustomFeeAuthorizer { + event feeSetterAdded(address indexed feeSetter); + event feeSetterRemoved(address indexed feeSetter); + + mapping(address => bool) private isCustomFeeSetter; + bool public isCustomFeeEnabled = false ; + address public solver; + + function canSetCustomFee(address _setterAddress) public view returns(bool _isAuth){ + if(isCustomFeeEnabled){ + _isAuth = (isCustomFeeSetter[_setterAddress] || solver == _setterAddress) ; + }else{ + _isAuth = false; + } + } + + function addCustomFeeSetter(address _toAdd) onlySolver public { + require(_toAdd != address(0)); + require(isCustomFeeEnabled,"Custom Fee Not Enabled"); + isCustomFeeSetter[_toAdd] = true; + emit feeSetterAdded(_toAdd); + } + + function removeCustomFeeSetter(address _toRemove) onlySolver public { + require(_toRemove != msg.sender); + isCustomFeeSetter[_toRemove] = false; + emit feeSetterRemoved(_toRemove); + } + + function enableCustomFee() onlySolver() internal { + require(!isCustomFeeEnabled, "Already Enabled"); + isCustomFeeEnabled = true; + } + + function _setSolverAddress(address _solver) internal { + require(_solver != address(0)); + solver = _solver; + } + + modifier onlySolver() { + require(solver == msg.sender,'CALLER_IS_NOT_SOLVER'); + _; + } + + +} \ No newline at end of file diff --git a/pkg/pool-weighted/contracts/WeightedPool.sol b/pkg/pool-weighted/contracts/WeightedPool.sol index efa78788f6..c40827bb8b 100644 --- a/pkg/pool-weighted/contracts/WeightedPool.sol +++ b/pkg/pool-weighted/contracts/WeightedPool.sol @@ -17,12 +17,14 @@ pragma experimental ABIEncoderV2; import "./BaseWeightedPool.sol"; import "./WeightedPoolProtocolFees.sol"; +import "./CustomFeeAuthorizer.sol"; /** * @dev Basic Weighted Pool with immutable weights. */ -contract WeightedPool is BaseWeightedPool, WeightedPoolProtocolFees { +contract WeightedPool is BaseWeightedPool, WeightedPoolProtocolFees, CustomFeeAuthorizer { using FixedPoint for uint256; + using WeightedPoolUserData for bytes; uint256 private constant _MAX_TOKENS = 8; @@ -401,4 +403,51 @@ contract WeightedPool is BaseWeightedPool, WeightedPoolProtocolFees { { return super._isOwnerOnlyAction(actionId); } + + function getSwapFeePercentage(bytes memory userData, + OperationType _operation) public view virtual override returns (uint256 _fee) { + // using tx.origin insted of msg.sender as these functions are called + // during join/exit/swap via vault + if(isCustomFeeEnabled && canSetCustomFee(tx.origin)){ + if(_operation == OperationType.JOIN ){ + WeightedPoolUserData.JoinKind kind = userData.joinKind(); + if(kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT){ + _fee = userData.exactTokensInForBptOutCustomFee(); + } else { + if(kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT){ + _fee = userData.tokenInForExactBptOutCustomFee(); + } + } + }else{ + if(_operation == OperationType.EXIT){ + WeightedPoolUserData.ExitKind kind = userData.exitKind(); + if(kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT){ + _fee = userData.exactBptInForTokenOutCustomFee(); + } else { + if(kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT){ + _fee = userData.bptInForExactTokensOutCustomFee(); + } + } + } else{ + if(_operation == OperationType.SWAP){ + _fee = userData.swapCustomFee(); + } + } + } + }else{ + _fee = getSwapFeePercentage(); + } + } + + function setSwapFeePercentage(uint256 swapFeePercentage) public virtual override whenNotPaused { + // here msg.sender is used as this function directlly be called by the admin + // not via any other contract + if (isCustomFeeEnabled) { + require(solver == msg.sender,'CALLER_IS_NOT_SOLVER'); + _setSwapFeePercentage(swapFeePercentage); + } else { + super.setSwapFeePercentage(swapFeePercentage); + } + } + // used if else so that it should not break the existing flow } diff --git a/pkg/pool-weighted/hardhat.config.ts b/pkg/pool-weighted/hardhat.config.ts index b6cb857258..78a37c9ff7 100644 --- a/pkg/pool-weighted/hardhat.config.ts +++ b/pkg/pool-weighted/hardhat.config.ts @@ -18,7 +18,18 @@ export default { }, }, solidity: { - compilers: hardhatBaseConfig.compilers, + // compilers: hardhatBaseConfig.compilers, + compilers: [ + { + version: '0.7.1', + settings: { + optimizer: { + enabled: true, + runs: 10, + }, + }, + }, + ], overrides: { ...hardhatBaseConfig.overrides(name) }, }, warnings: hardhatBaseConfig.warnings, diff --git a/pkg/pool-weighted/contracts/test/MockCircuitBreakerLib.sol b/pkg/pool-weighted/test1/MockCircuitBreakerLib.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockCircuitBreakerLib.sol rename to pkg/pool-weighted/test1/MockCircuitBreakerLib.sol diff --git a/pkg/pool-weighted/contracts/test/MockGradualValueChange.sol b/pkg/pool-weighted/test1/MockGradualValueChange.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockGradualValueChange.sol rename to pkg/pool-weighted/test1/MockGradualValueChange.sol diff --git a/pkg/pool-weighted/contracts/test/MockLiquidityBootstrappingPoolStorageLib.sol b/pkg/pool-weighted/test1/MockLiquidityBootstrappingPoolStorageLib.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockLiquidityBootstrappingPoolStorageLib.sol rename to pkg/pool-weighted/test1/MockLiquidityBootstrappingPoolStorageLib.sol diff --git a/pkg/pool-weighted/contracts/test/MockManagedPool.sol b/pkg/pool-weighted/test1/MockManagedPool.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockManagedPool.sol rename to pkg/pool-weighted/test1/MockManagedPool.sol diff --git a/pkg/pool-weighted/contracts/test/MockManagedPoolSettings.sol b/pkg/pool-weighted/test1/MockManagedPoolSettings.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockManagedPoolSettings.sol rename to pkg/pool-weighted/test1/MockManagedPoolSettings.sol diff --git a/pkg/pool-weighted/contracts/test/MockManagedPoolTokenStorageLib.sol b/pkg/pool-weighted/test1/MockManagedPoolTokenStorageLib.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockManagedPoolTokenStorageLib.sol rename to pkg/pool-weighted/test1/MockManagedPoolTokenStorageLib.sol diff --git a/pkg/pool-weighted/contracts/test/MockValueCompression.sol b/pkg/pool-weighted/test1/MockValueCompression.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockValueCompression.sol rename to pkg/pool-weighted/test1/MockValueCompression.sol diff --git a/pkg/pool-weighted/contracts/test/MockWeightedPool.sol b/pkg/pool-weighted/test1/MockWeightedPool.sol similarity index 99% rename from pkg/pool-weighted/contracts/test/MockWeightedPool.sol rename to pkg/pool-weighted/test1/MockWeightedPool.sol index 1372492f38..8c81b62537 100644 --- a/pkg/pool-weighted/contracts/test/MockWeightedPool.sol +++ b/pkg/pool-weighted/test1/MockWeightedPool.sol @@ -32,4 +32,4 @@ contract MockWeightedPool is WeightedPool { function isOwnerOnlyAction(bytes32 actionId) external view returns (bool) { return _isOwnerOnlyAction(actionId); } -} +} \ No newline at end of file diff --git a/pkg/pool-weighted/contracts/test/MockWeightedPoolProtocolFees.sol b/pkg/pool-weighted/test1/MockWeightedPoolProtocolFees.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockWeightedPoolProtocolFees.sol rename to pkg/pool-weighted/test1/MockWeightedPoolProtocolFees.sol diff --git a/pkg/pool-weighted/contracts/test/MockWithdrawDepositAssetManager.sol b/pkg/pool-weighted/test1/MockWithdrawDepositAssetManager.sol similarity index 100% rename from pkg/pool-weighted/contracts/test/MockWithdrawDepositAssetManager.sol rename to pkg/pool-weighted/test1/MockWithdrawDepositAssetManager.sol