From 8458022a3ca5753afac250c06fa6987eea50cd9d Mon Sep 17 00:00:00 2001 From: owen05 Date: Mon, 23 Nov 2020 10:43:12 +0800 Subject: [PATCH] remove some todo --- contracts/DODOPrivatePool/impl/DPP.sol | 21 ++++++---- contracts/DODOPrivatePool/impl/DPPTrader.sol | 5 +-- contracts/DODOPrivatePool/impl/DPPVault.sol | 26 ++++-------- contracts/DODOPrivatePool/intf/IDPP.sol | 23 ++++------- contracts/DODOVendingMachine/impl/DVM.sol | 8 ++-- .../DODOVendingMachine/impl/DVMFunding.sol | 32 ++++++++------- .../DODOVendingMachine/impl/DVMStorage.sol | 10 ++--- .../DODOVendingMachine/impl/DVMTrader.sol | 7 ++-- contracts/Factory/DPPFactory.sol | 37 ++++++++--------- contracts/Factory/DVMFactory.sol | 41 +++++++++++++------ contracts/SmartRoute/DVMProxy.sol | 2 +- contracts/lib/ConstFeeRateModel.sol | 4 +- 12 files changed, 109 insertions(+), 107 deletions(-) diff --git a/contracts/DODOPrivatePool/impl/DPP.sol b/contracts/DODOPrivatePool/impl/DPP.sol index 5d2e65c..94b1273 100644 --- a/contracts/DODOPrivatePool/impl/DPP.sol +++ b/contracts/DODOPrivatePool/impl/DPP.sol @@ -26,8 +26,13 @@ contract DPP is DPPTrader { address maintainer, address baseTokenAddress, address quoteTokenAddress, - address iSmartApprove, - address[] memory configAddresses + address lpFeeRateModel, + address mtFeeRateModel, + address kSource, + address iSource, + address gasPriceSource, + address tradePermissionManager, + address iSmartApprove ) external { require(msg.sender == _FACTORY_, 'INIT FORBIDDEN'); initOwner(owner); @@ -35,12 +40,12 @@ contract DPP is DPPTrader { _BASE_TOKEN_ = IERC20(baseTokenAddress); _QUOTE_TOKEN_ = IERC20(quoteTokenAddress); _DODO_SMART_APPROVE_ = ISmartApprove(iSmartApprove); - _LP_FEE_RATE_MODEL_ = IFeeRateModel(configAddresses[0]); - _MT_FEE_RATE_MODEL_ = IFeeRateModel(configAddresses[1]); - _GAS_PRICE_LIMIT_ = IExternalValue(configAddresses[2]); - _I_ = IExternalValue(configAddresses[3]); - _K_ = IExternalValue(configAddresses[4]); - _TRADE_PERMISSION_ = IPermissionManager(configAddresses[5]); + _LP_FEE_RATE_MODEL_ = IFeeRateModel(lpFeeRateModel); + _MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel); + _I_ = IExternalValue(iSource); + _K_ = IExternalValue(kSource); + _GAS_PRICE_LIMIT_ = IExternalValue(gasPriceSource); + _TRADE_PERMISSION_ = IPermissionManager(tradePermissionManager); _resetTargetAndReserve(); } diff --git a/contracts/DODOPrivatePool/impl/DPPTrader.sol b/contracts/DODOPrivatePool/impl/DPPTrader.sol index 4417f5b..ba64c4d 100644 --- a/contracts/DODOPrivatePool/impl/DPPTrader.sol +++ b/contracts/DODOPrivatePool/impl/DPPTrader.sol @@ -47,8 +47,7 @@ contract DPPTrader is DPPVault { uint256 mtFee; uint256 newBaseTarget; PMMPricing.RState newRState; - //TODO: confirm - (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(to, baseInput); + (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput); _transferQuoteOut(to, receiveQuoteAmount); _transferQuoteOut(_MAINTAINER_, mtFee); @@ -76,7 +75,7 @@ contract DPPTrader is DPPVault { uint256 newQuoteTarget; PMMPricing.RState newRState; - (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(to,quoteInput); + (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(tx.origin,quoteInput); _transferBaseOut(to, receiveBaseAmount); _transferBaseOut(_MAINTAINER_, mtFee); diff --git a/contracts/DODOPrivatePool/impl/DPPVault.sol b/contracts/DODOPrivatePool/impl/DPPVault.sol index bfd802c..758777e 100644 --- a/contracts/DODOPrivatePool/impl/DPPVault.sol +++ b/contracts/DODOPrivatePool/impl/DPPVault.sol @@ -39,7 +39,6 @@ contract DPPVault is DPPStorage { // ============ Set Status ============ - //TODO:对应前端哪个操作? function setTarget(uint256 baseTarget, uint256 quoteTarget) public onlyOwner { _BASE_TARGET_ = baseTarget; _QUOTE_TARGET_ = quoteTarget; @@ -62,11 +61,16 @@ contract DPPVault is DPPStorage { uint256 newLpFeeRate, uint256 newMtFeeRate, uint256 newI, - uint256 newK + uint256 newK, + uint256 baseOutAmount, + uint256 quoteOutAmount, + address to ) public { - //TODO: 讨论 + //TODO: owner 权限可以是operator require(msg.sender == _DODO_SMART_APPROVE_.getSmartSwap() || msg.sender == _OWNER_, "RESET FORBIDDEN!"); require(newK > 0 && newK <= 10**18, "K OUT OF RANGE!"); + if(baseOutAmount > 0) _transferBaseOut(to, baseOutAmount); + if(quoteOutAmount > 0) _transferQuoteOut(to, quoteOutAmount); _resetTargetAndReserve(); _LP_FEE_RATE_MODEL_.setFeeRate(newLpFeeRate); _MT_FEE_RATE_MODEL_.setFeeRate(newMtFeeRate); @@ -83,22 +87,6 @@ contract DPPVault is DPPStorage { // ============ Assets Transfer ============ - // function withdraw( - // address to, - // uint256 baseAmount, - // uint256 quoteAmount, - // bytes calldata data - // ) public onlyOwner { - // _transferBaseOut(to, baseAmount); - // _transferQuoteOut(to, quoteAmount); - // _BASE_TARGET_ = _BASE_TARGET_.sub(baseAmount); - // _QUOTE_TARGET_ = _QUOTE_TARGET_.sub(quoteAmount); - // _syncReserve(); - // if (data.length > 0) { - // IDODOCallee(to).DPPWithdrawCall(msg.sender, baseAmount, quoteAmount, data); - // } - // } - function _transferBaseOut(address to, uint256 amount) internal { if (amount > 0) { _BASE_TOKEN_.safeTransfer(to, amount); diff --git a/contracts/DODOPrivatePool/intf/IDPP.sol b/contracts/DODOPrivatePool/intf/IDPP.sol index 16761be..8a35e94 100644 --- a/contracts/DODOPrivatePool/intf/IDPP.sol +++ b/contracts/DODOPrivatePool/intf/IDPP.sol @@ -9,26 +9,17 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; interface IDPP { - // function init( - // address owner, - // address maintainer, - // address baseTokenAddress, - // address quoteTokenAddress, - // address lpFeeRateModel, - // address mtFeeRateModel, - // address tradePermissionManager, - // address gasPriceSource, - // address iSource, - // address kSource, - // address iSmartApprove - // ) external; - function init( address owner, address maintainer, address baseTokenAddress, address quoteTokenAddress, - address iSmartApprove, - address[] memory configAddresses + address lpFeeRateModel, + address mtFeeRateModel, + address kSource, + address iSource, + address gasPriceSource, + address tradePermissionManager, + address iSmartApprove ) external; } diff --git a/contracts/DODOVendingMachine/impl/DVM.sol b/contracts/DODOVendingMachine/impl/DVM.sol index 5f95151..64a7e93 100644 --- a/contracts/DODOVendingMachine/impl/DVM.sol +++ b/contracts/DODOVendingMachine/impl/DVM.sol @@ -7,8 +7,8 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -//TODO:讨论 是否应使用FeeRateModel -import {IConstFeeRateModel} from "../../lib/ConstFeeRateModel.sol"; + +import {IFeeRateModel} from "../../lib/FeeRateModel.sol"; import {IPermissionManager} from "../../lib/PermissionManager.sol"; import {IExternalValue} from "../../lib/ExternalValue.sol"; import {IERC20} from "../../intf/IERC20.sol"; @@ -32,8 +32,8 @@ contract DVM is DVMTrader, DVMFunding { initOwner(owner); _BASE_TOKEN_ = IERC20(baseTokenAddress); _QUOTE_TOKEN_ = IERC20(quoteTokenAddress); - _LP_FEE_RATE_MODEL_ = IConstFeeRateModel(lpFeeRateModel); - _MT_FEE_RATE_MODEL_ = IConstFeeRateModel(mtFeeRateModel); + _LP_FEE_RATE_MODEL_ = IFeeRateModel(lpFeeRateModel); + _MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel); _TRADE_PERMISSION_ = IPermissionManager(tradePermissionManager); _GAS_PRICE_LIMIT_ = IExternalValue(gasPriceSource); _MAINTAINER_ = maintainer; diff --git a/contracts/DODOVendingMachine/impl/DVMFunding.sol b/contracts/DODOVendingMachine/impl/DVMFunding.sol index d185d85..4718fd3 100644 --- a/contracts/DODOVendingMachine/impl/DVMFunding.sol +++ b/contracts/DODOVendingMachine/impl/DVMFunding.sol @@ -17,7 +17,7 @@ contract DVMFunding is DVMVault { uint256 baseInput = getBaseInput(); uint256 quoteInput = getQuoteInput(); require(baseInput > 0, "NO_BASE_INPUT"); - uint256 baseReserve = _BASE_RESERVE_; // may save gas? 待确认 + uint256 baseReserve = _BASE_RESERVE_; uint256 quoteReserve = _QUOTE_RESERVE_; uint256 mintAmount; // case 1. initial supply @@ -31,7 +31,6 @@ contract DVMFunding is DVMVault { } // case 3. normal case if (baseReserve > 0 && quoteReserve > 0) { - //TODO: (Route合约配合实现) uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve); uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve); uint256 mintRatio = baseInputRatio > quoteInputRatio ? quoteInputRatio : baseInputRatio; @@ -48,27 +47,30 @@ contract DVMFunding is DVMVault { _sync(); } + //TODO:Router unwrap WETH function sellShares( - address to, - uint256 shareAmount, - bytes calldata data + address to + // uint256 shareAmount, + // bytes calldata data ) external preventReentrant returns (uint256) { - require(_SHARES_[msg.sender] >= shareAmount, "SHARES_NOT_ENOUGH"); + // require(_SHARES_[msg.sender] >= shareAmount, "SHARES_NOT_ENOUGH"); (uint256 baseBalance, uint256 quoteBalance) = getVaultBalance(); uint256 totalShares = totalSupply; - _burn(msg.sender, shareAmount); + uint256 shareAmount = _SHARES_[address(this)]; uint256 baseAmount = baseBalance.mul(shareAmount).div(totalShares); uint256 quoteAmount = quoteBalance.mul(shareAmount).div(totalShares); + require(baseAmount > 0 && quoteAmount > 0, 'NO_DLP_INPUT'); + _burn(address(this), shareAmount); _transferBaseOut(to, baseAmount); _transferQuoteOut(to, quoteAmount); _sync(); - if (data.length > 0) - IDODOCallee(msg.sender).DVMSellShareCall( - to, - shareAmount, - baseAmount, - quoteAmount, - data - ); + // if (data.length > 0) + // IDODOCallee(msg.sender).DVMSellShareCall( + // to, + // shareAmount, + // baseAmount, + // quoteAmount, + // data + // ); } } diff --git a/contracts/DODOVendingMachine/impl/DVMStorage.sol b/contracts/DODOVendingMachine/impl/DVMStorage.sol index 1993db7..0bfb276 100644 --- a/contracts/DODOVendingMachine/impl/DVMStorage.sol +++ b/contracts/DODOVendingMachine/impl/DVMStorage.sol @@ -15,7 +15,7 @@ import {DODOMath} from "../../lib/DODOMath.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; import {IPermissionManager} from "../../lib/PermissionManager.sol"; import {IExternalValue} from "../../lib/ExternalValue.sol"; -import {IConstFeeRateModel} from "../../lib/ConstFeeRateModel.sol"; +import {IFeeRateModel} from "../../lib/FeeRateModel.sol"; import {IERC20} from "../../intf/IERC20.sol"; contract DVMStorage is InitializableOwnable, ReentrancyGuard { @@ -54,8 +54,8 @@ contract DVMStorage is InitializableOwnable, ReentrancyGuard { // ============ Variables for Pricing ============ - IConstFeeRateModel public _LP_FEE_RATE_MODEL_; - IConstFeeRateModel public _MT_FEE_RATE_MODEL_; + IFeeRateModel public _LP_FEE_RATE_MODEL_; + IFeeRateModel public _MT_FEE_RATE_MODEL_; uint256 public _K_; uint256 public _I_; @@ -63,11 +63,11 @@ contract DVMStorage is InitializableOwnable, ReentrancyGuard { //TODO: owner权限问题论证 function setLpFeeRateModel(address newLpFeeRateModel) external onlyOwner { - _LP_FEE_RATE_MODEL_ = IConstFeeRateModel(newLpFeeRateModel); + _LP_FEE_RATE_MODEL_ = IFeeRateModel(newLpFeeRateModel); } function setMtFeeRateModel(address newMtFeeRateModel) external onlyOwner { - _MT_FEE_RATE_MODEL_ = IConstFeeRateModel(newMtFeeRateModel); + _MT_FEE_RATE_MODEL_ = IFeeRateModel(newMtFeeRateModel); } function setTradePermissionManager(address newTradePermissionManager) external onlyOwner { diff --git a/contracts/DODOVendingMachine/impl/DVMTrader.sol b/contracts/DODOVendingMachine/impl/DVMTrader.sol index 26993c8..4aac393 100644 --- a/contracts/DODOVendingMachine/impl/DVMTrader.sol +++ b/contracts/DODOVendingMachine/impl/DVMTrader.sol @@ -48,8 +48,8 @@ contract DVMTrader is DVMVault { returns (uint256 receiveQuoteAmount) { uint256 baseInput = getBaseInput(); + require(baseInput > 0, 'INSUFFICIENT_BASE_INPUT'); uint256 mtFee; - //TODO:tx.origin 的潜在风险,直接写to (receiveQuoteAmount, mtFee) = querySellBase(tx.origin, baseInput); _transferQuoteOut(to, receiveQuoteAmount); _transferQuoteOut(_MAINTAINER_, mtFee); @@ -65,6 +65,7 @@ contract DVMTrader is DVMVault { returns (uint256 receiveBaseAmount) { uint256 quoteInput = getQuoteInput(); + require(quoteInput > 0, 'INSUFFICIENT_QUOTE_INPUT'); uint256 mtFee; (receiveBaseAmount, mtFee) = querySellQuote(tx.origin, quoteInput); _transferBaseOut(to, receiveBaseAmount); @@ -98,7 +99,7 @@ contract DVMTrader is DVMVault { DecimalMath.ONE.sub(mtFeeRate).sub(lpFeeRate) ); baseBalance = baseReserve.sub(validBaseOut); - _transferBaseOut(_MAINTAINER_, DecimalMath.mulCeil(validBaseOut, mtFeeRate)); + _transferBaseOut(_MAINTAINER_, DecimalMath.mulFloor(validBaseOut, mtFeeRate)); } if (quoteBalance < quoteReserve) { uint256 validQuoteOut = DecimalMath.divCeil( @@ -106,7 +107,7 @@ contract DVMTrader is DVMVault { DecimalMath.ONE.sub(mtFeeRate).sub(lpFeeRate) ); quoteBalance = quoteReserve.sub(validQuoteOut); - _transferQuoteOut(_MAINTAINER_, DecimalMath.mulCeil(validQuoteOut, mtFeeRate)); + _transferQuoteOut(_MAINTAINER_, DecimalMath.mulFloor(validQuoteOut, mtFeeRate)); } require( diff --git a/contracts/Factory/DPPFactory.sol b/contracts/Factory/DPPFactory.sol index f88a4d1..a200b87 100644 --- a/contracts/Factory/DPPFactory.sol +++ b/contracts/Factory/DPPFactory.sol @@ -23,12 +23,15 @@ contract DPPFactory is Ownable { address public _PERMISSION_MANAGER_TEMPLATE_; address public _VALUE_SOURCE_; - struct DPPInfo{ + //TODO: 平台修改tag的权限 + struct DPPInfo { address creator; uint256 createTimeStamp; - //TODO:other tags + //TODO:池子类型 } + //TODO:tags filter event + // base -> quote -> DPP address list mapping(address => mapping(address => address[])) _REGISTRY_; // token0 -> token1 -> DPP address list @@ -57,31 +60,27 @@ contract DPPFactory is Ownable { function createStandardDODOPrivatePool( address baseToken, address quoteToken, - address[] memory valueTemplates, //feeeRateAddr,mtRateAddr,gasPriceAddr,kAddr,iAddr - uint256[] memory values // feeRate,mtRate,gasPrice,k,i + //TODO: tag 粒度 + address[] memory valueTemplates, //feeRateAddr,mtRateAddr,kAddr,iAddr + uint256[] memory values // feeRate,mtRate,k,i ) external returns (address newPrivatePool) { - require(valueTemplates.length == 5 && values.length == 5, "Incorrect number of initialization parameters"); - + require(valueTemplates.length == 4 && values.length == 4, "Incorrect number of initialization parameters"); (address token0, address token1) = baseToken < quoteToken ? (baseToken, quoteToken) : (quoteToken, baseToken); - uint256 len = _SORT_REGISTRY_[token0][token1].length; - bytes32 salt = keccak256(abi.encodePacked(token0, token1, len)); - newPrivatePool = ICloneFactory(_CLONE_FACTORY_).clone2(_DPP_TEMPLATE_,salt); - - address[] memory configAddresses = new address[](6); - configAddresses[0] = (valueTemplates[0] == address(0) ? createFeeRateModel(newPrivatePool, values[0]) : valueTemplates[0]); - configAddresses[1] = (valueTemplates[1] == address(0) ? createFeeRateModel(newPrivatePool, values[1]) : valueTemplates[1]); - configAddresses[2] = (valueTemplates[2] == address(0) ? createExternalValueModel(newPrivatePool, values[2]) : valueTemplates[2]); - configAddresses[3] = (valueTemplates[3] == address(0) ? createExternalValueModel(newPrivatePool, values[3]) : valueTemplates[3]); - configAddresses[4] = (valueTemplates[4] == address(0) ? createExternalValueModel(newPrivatePool, values[4]) : valueTemplates[4]); - configAddresses[5] = createPermissionManager(msg.sender); + newPrivatePool = ICloneFactory(_CLONE_FACTORY_).clone(_DPP_TEMPLATE_); IDPP(newPrivatePool).init( msg.sender, msg.sender, baseToken, quoteToken, - _DODO_SMART_APPROVE_, - configAddresses + (valueTemplates[0] == address(0) ? createFeeRateModel(newPrivatePool, values[0]) : valueTemplates[0]), + (valueTemplates[1] == address(0) ? createFeeRateModel(newPrivatePool, values[1]) : valueTemplates[1]), + (valueTemplates[2] == address(0) ? createExternalValueModel(newPrivatePool, values[2]) : valueTemplates[2]), + (valueTemplates[3] == address(0) ? createExternalValueModel(newPrivatePool, values[3]) : valueTemplates[3]), + //hardcode + createExternalValueModel(msg.sender, 10**22), + createPermissionManager(msg.sender), + _DODO_SMART_APPROVE_ ); _REGISTRY_[baseToken][quoteToken].push(newPrivatePool); diff --git a/contracts/Factory/DVMFactory.sol b/contracts/Factory/DVMFactory.sol index df99dea..9472dac 100644 --- a/contracts/Factory/DVMFactory.sol +++ b/contracts/Factory/DVMFactory.sol @@ -19,11 +19,22 @@ contract DVMFactory is Ownable { address public _DVM_TEMPLATE_; address public _FEE_RATE_MODEL_TEMPLATE_; address public _PERMISSION_MANAGER_TEMPLATE_; - address public _DEFAULT_GAS_PRICE_SOURCE_; + struct DVMInfo { + address creator; + uint256 createTimeStamp; + //TODO:other tags + } + // base -> quote -> DVM address list mapping(address => mapping(address => address[])) _REGISTRY_; + // token0 -> token1 -> DVM address list + mapping(address => mapping(address => address[])) _SORT_REGISTRY_; + // creator -> DVM address list + mapping(address => address[]) _USER_REGISTRY_; + // DVM address -> info + mapping(address => DVMInfo) _DVM_INFO_; constructor( address cloneFactory, @@ -46,27 +57,33 @@ contract DVMFactory is Ownable { uint256 mtFeeRate, uint256 i, uint256 k - ) external returns (address newVendorMachine) { - newVendorMachine = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_); + ) external returns (address newVendingMachine) { + (address token0, address token1) = baseToken < quoteToken ? (baseToken, quoteToken) : (quoteToken, baseToken); + newVendingMachine = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_); - IDVM(newVendorMachine).init( + IDVM(newVendingMachine).init( msg.sender, msg.sender, baseToken, quoteToken, - createConstFeeRateModel(msg.sender, lpFeeRate), - createConstFeeRateModel(msg.sender, mtFeeRate), + createConstFeeRateModel(newVendingMachine, lpFeeRate), + createConstFeeRateModel(newVendingMachine, mtFeeRate), createPermissionManager(msg.sender), _DEFAULT_GAS_PRICE_SOURCE_, i, k ); - //TODO: Create2 - //TODO: DVM作为Mapping的字段,维护自身属性 - //TODO: 创建者索引,便于my pool查询 - _REGISTRY_[baseToken][quoteToken].push(newVendorMachine); - return newVendorMachine; + _REGISTRY_[baseToken][quoteToken].push(newVendingMachine); + _SORT_REGISTRY_[token0][token1].push(newVendingMachine); + _USER_REGISTRY_[msg.sender].push(newVendingMachine); + _DVM_INFO_[newVendingMachine] = ( + DVMInfo({ + creator: msg.sender, + createTimeStamp: block.timestamp + }) + ); + return newVendingMachine; } function createConstFeeRateModel(address owner, uint256 feeRate) @@ -84,7 +101,7 @@ contract DVMFactory is Ownable { return permissionManager; } - function getVendorMachine(address baseToken, address quoteToken) + function getVendingMachine(address baseToken, address quoteToken) external view returns (address[] memory machines) diff --git a/contracts/SmartRoute/DVMProxy.sol b/contracts/SmartRoute/DVMProxy.sol index 4aaf56a..6e4fc9c 100644 --- a/contracts/SmartRoute/DVMProxy.sol +++ b/contracts/SmartRoute/DVMProxy.sol @@ -25,7 +25,7 @@ contract DVMProxy { //TODO: addLiquidityToDVM - //TODO: removeLiquidityToDVM + //TODO: removeLiquidityToDVM(待定) //TODO: createDPP diff --git a/contracts/lib/ConstFeeRateModel.sol b/contracts/lib/ConstFeeRateModel.sol index f5744bd..546d4aa 100644 --- a/contracts/lib/ConstFeeRateModel.sol +++ b/contracts/lib/ConstFeeRateModel.sol @@ -13,7 +13,7 @@ import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; interface IConstFeeRateModel { function init(address owner, uint256 feeRate) external; function setFeeRate(uint256 newFeeRate) external; - function getFeeRate(address trader) external view returns (uint256); + function getFeeRate() external view returns (uint256); } contract ConstFeeRateModel is InitializableOwnable { @@ -28,7 +28,7 @@ contract ConstFeeRateModel is InitializableOwnable { _FEE_RATE_ = newFeeRate; } - function getFeeRate(address trader) external view returns (uint256) { + function getFeeRate() external view returns (uint256) { return _FEE_RATE_; } }