diff --git a/contracts/CallAuction/impl/CAFunding.sol b/contracts/CallAuction/impl/CAFunding.sol deleted file mode 100644 index a9899f0..0000000 --- a/contracts/CallAuction/impl/CAFunding.sol +++ /dev/null @@ -1,158 +0,0 @@ -/* - - Copyright 2020 DODO ZOO. - SPDX-License-Identifier: Apache-2.0 - -*/ - -pragma solidity 0.6.9; -pragma experimental ABIEncoderV2; - -import {SafeMath} from "../../lib/SafeMath.sol"; -import {SafeERC20} from "../../lib/SafeERC20.sol"; -import {DecimalMath} from "../../lib/DecimalMath.sol"; -import {IERC20} from "../../intf/IERC20.sol"; -import {IDODOCallee} from "../../intf/IDODOCallee.sol"; -import {CAStorage} from "./CAStorage.sol"; -import {PMMPricing} from "../../lib/PMMPricing.sol"; - -contract CAFunding is CAStorage { - using SafeERC20 for IERC20; - - // ============ PRE BID PHASE ============ - - // in case deposit base amount too much - function withdrawBaseToken(address to, uint256 amount) external phasePreBid onlyOwner { - _transferQuoteOut(to, amount); - } - - // ============ BID & CALM PHASE ============ - - modifier isBidderAllow(address bidder) { - require(_BIDDER_PERMISSION_.isAllowed(bidder), "BIDDER_NOT_ALLOWED"); - _; - } - - function bid(address to) external phaseBid preventReentrant isBidderAllow(to) { - uint256 input = _getQuoteInput(); - uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); - _transferQuoteOut(_MAINTAINER_, mtFee); - _QUOTE_SHARES_[to] = _QUOTE_SHARES_[to].add(input.sub(mtFee)); - _TOTAL_QUOTE_SHARES_ = _TOTAL_QUOTE_SHARES_.add(input.sub(mtFee)); - _sync(); - } - - function cancel(address to, uint256 amount) external phaseBidOrCalm preventReentrant { - require(_QUOTE_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); - _QUOTE_SHARES_[msg.sender] = _QUOTE_SHARES_[msg.sender].sub(amount); - _transferQuoteOut(to, amount); - _sync(); - } - - // ============ SETTLEMENT ============ - - function settle() external phaseSettlement preventReentrant { - require(!_SETTLED_, "ALREADY_SETTLED"); - _SETTLED_ = true; - - uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); - uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); - - // 1. sold base remaining in the contract - _TOTAL_SOLD_BASE_ = getBaseSold(); - - // 2. left base send out - _transferBaseOut(_BASE_PAY_BACK_, baseBalance.sub(_TOTAL_SOLD_BASE_)); - - // 3. used quote token - uint256 usedQuote = _QUOTE_CAP_ <= quoteBalance ? _QUOTE_CAP_ : quoteBalance; - uint256 ownerQuote = DecimalMath.mulFloor(usedQuote, _OWNER_RATIO_); - _transferQuoteOut(_OWNER_, ownerQuote); - _transferQuoteOut(_QUOTE_PAY_BACK_, usedQuote.sub(usedQuote)); - - // 4. leave unused quote token in contract - _TOTAL_UNUSED_QUOTE_ = quoteBalance.sub(usedQuote); - - // 5. external call - if (block.timestamp < _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRED_TIME_)) { - if (_BASE_PAY_BACK_CALL_DATA_.length > 0) { - (bool success, ) = _BASE_PAY_BACK_.call(_BASE_PAY_BACK_CALL_DATA_); - require(success, "BASE_PAY_BACK_CALL_FAILED"); - } - if (_QUOTE_PAY_BACK_CALL_DATA_.length > 0) { - (bool success, ) = _QUOTE_PAY_BACK_.call(_QUOTE_PAY_BACK_CALL_DATA_); - require(success, "QUOTE_PAY_BACK_CALL_FAILED"); - } - } - } - - // in case something wrong with base token contract - function emergencySettle() external phaseSettlement preventReentrant { - require(!_SETTLED_, "ALREADY_SETTLED"); - require( - block.timestamp > _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRED_TIME_), - "NOT_EMERGENCY" - ); - _SETTLED_ = true; - _TOTAL_UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); - } - - // ============ Pricing ============ - - function getAvgPrice() public view returns (uint256 avgPrice) { - uint256 baseSold = getBaseSold(); - avgPrice = DecimalMath.divFloor(_QUOTE_TOKEN_.balanceOf(address(this)), baseSold); - } - - function getBaseByUser(address user) public view returns (uint256 baseAmount) { - uint256 baseSold = getBaseSold(); - baseAmount = baseSold.mul(_QUOTE_SHARES_[user]).div(_TOTAL_QUOTE_SHARES_); - } - - function getBaseSold() public view returns (uint256 baseSold) { - uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); - if (quoteBalance > _QUOTE_CAP_) { - quoteBalance = _QUOTE_CAP_; - } - (baseSold, ) = PMMPricing.sellQuoteToken(_getPMMState(), quoteBalance); - } - - function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { - state.i = _I_; - state.K = _K_; - state.B = _BASE_TOKEN_.balanceOf(address(this)); - state.Q = 0; - state.B0 = state.B; - state.Q0 = 0; - state.R = PMMPricing.RState.ONE; - } - - // ============ Asset In ============ - - function _getQuoteInput() internal view returns (uint256 input) { - return _QUOTE_TOKEN_.balanceOf(address(this)).sub(_QUOTE_RESERVE_); - } - - // ============ Set States ============ - - function _sync() internal { - uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); - if (quoteBalance != _QUOTE_RESERVE_) { - _QUOTE_RESERVE_ = quoteBalance; - } - } - - // ============ Asset Out ============ - - function _transferBaseOut(address to, uint256 amount) internal { - if (amount > 0) { - _BASE_TOKEN_.safeTransfer(to, amount); - } - } - - function _transferQuoteOut(address to, uint256 amount) internal { - if (amount > 0) { - _QUOTE_TOKEN_.safeTransfer(to, amount); - } - } -} diff --git a/contracts/CallAuction/impl/CAVesting.sol b/contracts/CallAuction/impl/CAVesting.sol deleted file mode 100644 index 816f1b9..0000000 --- a/contracts/CallAuction/impl/CAVesting.sol +++ /dev/null @@ -1,88 +0,0 @@ -/* - - Copyright 2020 DODO ZOO. - SPDX-License-Identifier: Apache-2.0 - -*/ - -pragma solidity 0.6.9; -pragma experimental ABIEncoderV2; - -import {SafeMath} from "../../lib/SafeMath.sol"; -import {DecimalMath} from "../../lib/DecimalMath.sol"; -import {Ownable} from "../../lib/Ownable.sol"; -import {SafeERC20} from "../../lib/SafeERC20.sol"; -import {IERC20} from "../../intf/IERC20.sol"; -import {CAFunding} from "./CAFunding.sol"; - -/** - * @title CAVesting - * @author DODO Breeder - * - * @notice Lock Token and release it linearly - */ - -contract CAVesting is CAFunding { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - modifier afterSettlement() { - require(_SETTLED_, "NOT_SETTLED"); - _; - } - - // ============ Functions ============ - - function claimBase() external afterSettlement { - uint256 claimableToken = getClaimableBaseBalance(msg.sender); - _transferBaseOut(msg.sender, claimableToken); - _CLAIMED_BASE_[msg.sender] = _CLAIMED_BASE_[msg.sender].add(claimableToken); - } - - function claimQuote() external afterSettlement { - require(!_QUOTE_CLAIMED_[msg.sender], "QUOTE_CLAIMED"); - _QUOTE_CLAIMED_[msg.sender] = true; - _transferQuoteOut(msg.sender, getClaimableQuoteBalance(msg.sender)); - } - - // ============ View ============ - - function getOriginBaseBalance(address user) public view returns (uint256) { - return _TOTAL_SOLD_BASE_.mul(_QUOTE_SHARES_[user]).div(_TOTAL_QUOTE_SHARES_); - } - - function getClaimedBaseBalance(address holder) public view returns (uint256) { - return _CLAIMED_BASE_[holder]; - } - - function getClaimableBaseBalance(address holder) public view returns (uint256) { - uint256 remainingToken = getRemainingBaseBalance(holder); - return getOriginBaseBalance(holder).sub(remainingToken).sub(_CLAIMED_BASE_[holder]); - } - - function getRemainingBaseBalance(address holder) public view returns (uint256) { - uint256 remainingRatio = getRemainingBaseRatio(block.timestamp); - return DecimalMath.mulFloor(getOriginBaseBalance(holder), remainingRatio); - } - - function getRemainingBaseRatio(uint256 timestamp) public view returns (uint256) { - if (timestamp < _START_VESTING_TIME_) { - return DecimalMath.ONE; - } - uint256 timePast = timestamp.sub(_START_VESTING_TIME_); - if (timePast < _VESTING_DURATION_) { - uint256 remainingTime = _VESTING_DURATION_.sub(timePast); - return DecimalMath.ONE.sub(_CLIFF_RATE_).mul(remainingTime).div(_VESTING_DURATION_); - } else { - return 0; - } - } - - function getClaimableQuoteBalance(address holder) public view returns (uint256) { - if (!_QUOTE_CLAIMED_[msg.sender]) { - return 0; - } else { - return _TOTAL_UNUSED_QUOTE_.mul(_QUOTE_SHARES_[holder]).div(_TOTAL_QUOTE_SHARES_); - } - } -} diff --git a/contracts/Factory/DVMFactory.sol b/contracts/Factory/DVMFactory.sol index 8a0d76c..17803f1 100644 --- a/contracts/Factory/DVMFactory.sol +++ b/contracts/Factory/DVMFactory.sol @@ -15,6 +15,18 @@ import {IDVM} from "../DODOVendingMachine/intf/IDVM.sol"; import {IDVMAdmin} from "../DODOVendingMachine/intf/IDVMAdmin.sol"; import {IPermissionManager} from "../lib/PermissionManager.sol"; +interface IDVMFactory { + function createDODOVendingMachine( + address creator, + address baseToken, + address quoteToken, + uint256 lpFeeRate, + uint256 mtFeeRate, + uint256 i, + uint256 k + ) external returns (address newVendingMachine); +} + contract DVMFactory is Ownable { // ============ Templates ============ diff --git a/contracts/CallAuction/impl/CA.sol b/contracts/PoolKickstarter/impl/CA.sol similarity index 100% rename from contracts/CallAuction/impl/CA.sol rename to contracts/PoolKickstarter/impl/CA.sol diff --git a/contracts/PoolKickstarter/impl/CAFunding.sol b/contracts/PoolKickstarter/impl/CAFunding.sol new file mode 100644 index 0000000..401cbfb --- /dev/null +++ b/contracts/PoolKickstarter/impl/CAFunding.sol @@ -0,0 +1,191 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {IDVM} from "../../DODOVendingMachine/intf/IDVM.sol"; +import {IDVMFactory} from "../../Factory/DVMFactory.sol"; +import {CAStorage} from "./CAStorage.sol"; +import {PMMPricing} from "../../lib/PMMPricing.sol"; + +contract CAFunding is CAStorage { + using SafeERC20 for IERC20; + + // ============ BID & CALM PHASE ============ + + modifier isBidderAllow(address bidder) { + require(_BIDDER_PERMISSION_.isAllowed(bidder), "BIDDER_NOT_ALLOWED"); + _; + } + + function bid(address to) external phaseBid preventReentrant isBidderAllow(to) { + uint256 input = _getQuoteInput(); + uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); + _transferQuoteOut(_MAINTAINER_, mtFee); + _mintShares(to, input.sub(mtFee)); + _sync(); + } + + function cancel(address assetTo, uint256 amount) external phaseBidOrCalm preventReentrant { + require(_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); + _burnShares(msg.sender, amount); + _transferQuoteOut(assetTo, amount); + _sync(); + } + + function _mintShares(address to, uint256 amount) internal { + _SHARES_[to] = _SHARES_[to].add(amount); + _TOTAL_SHARES_ = _TOTAL_SHARES_.add(amount); + } + + function _burnShares(address from, uint256 amount) internal { + _SHARES_[from] = _SHARES_[from].sub(amount); + _TOTAL_SHARES_ = _TOTAL_SHARES_.sub(amount); + } + + // ============ SETTLEMENT ============ + + function settle() external phaseSettlement preventReentrant { + require(!_SETTLED_, "ALREADY_SETTLED"); + _SETTLED_ = true; + + (uint256 poolBase, uint256 poolQuote, uint256 ownerQuote) = getSettleResult(); + _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)).sub(poolQuote).sub(ownerQuote); + _UNUSED_BASE_ = _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase); + uint256 avgPrice = DecimalMath.divCeil(poolQuote.add(ownerQuote), _UNUSED_BASE_); + + // 这里的目的是让开盘价尽量等于avgPrice + // 我们统一设定k=1,如果quote和base不平衡,就必然要截断一边 + // DVM截断了quote,所以如果进入池子的quote很多,就要把quote设置成DVM的base + // m = avgPrice + // i = m (1-quote/(m*base)) + // if quote = m*base i = 1 + // if quote > m*base reverse + uint256 baseDepth = DecimalMath.mulFloor(avgPrice, poolBase); + if (poolQuote == baseDepth) { + _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( + address(this), + address(_BASE_TOKEN_), + address(_QUOTE_TOKEN_), + 3e15, + 0, + 1, + DecimalMath.ONE + ); + } else if (poolQuote < baseDepth) { + uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divFloor(poolQuote, baseDepth)); + _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( + address(this), + address(_BASE_TOKEN_), + address(_QUOTE_TOKEN_), + 3e15, + 0, + avgPrice.mul(ratio).mul(ratio).divCeil(DecimalMath.ONE2), + DecimalMath.ONE + ); + } else if (poolQuote > baseDepth) { + uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divFloor(baseDepth, poolQuote)); + _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( + address(this), + address(_QUOTE_TOKEN_), + address(_BASE_TOKEN_), + 3e15, + 0, + DecimalMath.reciprocalFloor(avgPrice).mul(ratio).mul(ratio).divCeil( + DecimalMath.ONE2 + ), + DecimalMath.ONE + ); + } + + _transferBaseOut(_POOL_, poolBase); + _transferQuoteOut(_POOL_, poolQuote); + _transferQuoteOut(_OWNER_, ownerQuote); + + IDVM(_POOL_).buyShares(address(this)); + } + + // in case something wrong with base token contract + function emergencySettle() external phaseSettlement preventReentrant { + require(!_SETTLED_, "ALREADY_SETTLED"); + require( + block.timestamp > _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRED_TIME_), + "NOT_EMERGENCY" + ); + _SETTLED_ = true; + _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); + _UNUSED_BASE_ = _BASE_TOKEN_.balanceOf(address(this)); + } + + // ============ Pricing ============ + + function getSettleResult() + public + view + returns ( + uint256 poolBase, + uint256 poolQuote, + uint256 ownerQuote + ) + { + poolQuote = _QUOTE_TOKEN_.balanceOf(address(this)); + if (poolQuote > _POOL_QUOTE_CAP_) { + poolQuote = _POOL_QUOTE_CAP_; + } + (uint256 soldBase, ) = PMMPricing.sellQuoteToken(_getPMMState(), poolQuote); + poolBase = _TOTAL_BASE_.sub(soldBase); + if (poolBase < _POOL_BASE_RESERVE_) { + poolBase = _POOL_BASE_RESERVE_; + } + ownerQuote = DecimalMath.mulFloor(poolQuote, _OWNER_QUOTE_RATIO_); + poolQuote = poolQuote.sub(ownerQuote); + } + + function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { + state.i = _I_; + state.K = _K_; + state.B = _TOTAL_BASE_; + state.Q = 0; + state.B0 = state.B; + state.Q0 = 0; + state.R = PMMPricing.RState.ONE; + } + + // ============ Asset In ============ + + function _getQuoteInput() internal view returns (uint256 input) { + return _QUOTE_TOKEN_.balanceOf(address(this)).sub(_QUOTE_RESERVE_); + } + + // ============ Set States ============ + + function _sync() internal { + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + if (quoteBalance != _QUOTE_RESERVE_) { + _QUOTE_RESERVE_ = quoteBalance; + } + } + + // ============ Asset Out ============ + + function _transferBaseOut(address to, uint256 amount) internal { + if (amount > 0) { + _BASE_TOKEN_.safeTransfer(to, amount); + } + } + + function _transferQuoteOut(address to, uint256 amount) internal { + if (amount > 0) { + _QUOTE_TOKEN_.safeTransfer(to, amount); + } + } +} diff --git a/contracts/CallAuction/impl/CAStorage.sol b/contracts/PoolKickstarter/impl/CAStorage.sol similarity index 74% rename from contracts/CallAuction/impl/CAStorage.sol rename to contracts/PoolKickstarter/impl/CAStorage.sol index 75e7c6f..31c7565 100644 --- a/contracts/CallAuction/impl/CAStorage.sol +++ b/contracts/PoolKickstarter/impl/CAStorage.sol @@ -25,6 +25,7 @@ contract CAStorage is InitializableOwnable, ReentrancyGuard { uint256 _PHASE_BID_STARTTIME_; uint256 _PHASE_BID_ENDTIME_; uint256 _PHASE_CALM_ENDTIME_; + uint256 _FREEZE_DURATION_; bool _SETTLED_; // ============ Core Address ============ @@ -34,22 +35,26 @@ contract CAStorage is InitializableOwnable, ReentrancyGuard { // ============ Distribution Parameters ============ - uint256 _QUOTE_CAP_; - uint256 _OWNER_RATIO_; - address public _BASE_PAY_BACK_; - address public _QUOTE_PAY_BACK_; - bytes _BASE_PAY_BACK_CALL_DATA_; - bytes _QUOTE_PAY_BACK_CALL_DATA_; + uint256 _OWNER_QUOTE_RATIO_; // 抽取一部分 + uint256 _TOTAL_BASE_; - // ============ Balances ============ + uint256 _POOL_QUOTE_CAP_; + uint256 _POOL_BASE_RESERVE_; + + // ============ Settlement ============ uint256 public _QUOTE_RESERVE_; - uint256 public _TOTAL_SOLD_BASE_; - uint256 public _TOTAL_UNUSED_QUOTE_; - uint256 public _TOTAL_QUOTE_SHARES_; - mapping(address => uint256) internal _QUOTE_SHARES_; + + uint256 public _UNUSED_BASE_; + uint256 public _UNUSED_QUOTE_; + + uint256 public _TOTAL_SHARES_; + mapping(address => uint256) internal _SHARES_; mapping(address => bool) internal _QUOTE_CLAIMED_; - mapping(address => uint256) internal _CLAIMED_BASE_; + mapping(address => bool) internal _BASE_CLAIMED_; + + address _POOL_FACTORY_; + address _POOL_; // ============ Advanced Control ============ @@ -57,12 +62,6 @@ contract CAStorage is InitializableOwnable, ReentrancyGuard { IFeeRateModel public _MT_FEE_RATE_MODEL_; IPermissionManager public _BIDDER_PERMISSION_; - // ============ Time Lock ============ - - uint256 public _START_VESTING_TIME_; - uint256 public _VESTING_DURATION_; - uint256 public _CLIFF_RATE_; - // ============ PMM Parameters ============ uint256 public _K_; @@ -70,11 +69,6 @@ contract CAStorage is InitializableOwnable, ReentrancyGuard { // ============ Modifiers ============ - modifier phasePreBid() { - require(block.timestamp <= _PHASE_BID_STARTTIME_, "NOT_PHASE_PREBID"); - _; - } - modifier phaseBid() { require( block.timestamp > _PHASE_BID_STARTTIME_ && block.timestamp <= _PHASE_BID_ENDTIME_, diff --git a/contracts/PoolKickstarter/impl/CAVesting.sol b/contracts/PoolKickstarter/impl/CAVesting.sol new file mode 100644 index 0000000..4c3e8aa --- /dev/null +++ b/contracts/PoolKickstarter/impl/CAVesting.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {Ownable} from "../../lib/Ownable.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {CAFunding} from "./CAFunding.sol"; + +/** + * @title CAVesting + * @author DODO Breeder + * + * @notice Lock Token and release it linearly + */ + +contract CAVesting is CAFunding { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + modifier afterSettlement() { + require(_SETTLED_, "NOT_SETTLED"); + _; + } + + modifier afterFreeze() { + require(block.timestamp >= _PHASE_CALM_ENDTIME_.add(_FREEZE_DURATION_), "FREEZED"); + _; + } + + // ============ Functions ============ + + function claimBase() external afterSettlement { + require(!_BASE_CLAIMED_[msg.sender], "BASE_CLAIMED"); + _BASE_CLAIMED_[msg.sender] = true; + _transferBaseOut(msg.sender, _UNUSED_BASE_.mul(_SHARES_[msg.sender]).div(_TOTAL_SHARES_)); + } + + function claimQuote() external afterSettlement { + require(!_QUOTE_CLAIMED_[msg.sender], "QUOTE_CLAIMED"); + _QUOTE_CLAIMED_[msg.sender] = true; + _transferQuoteOut(msg.sender, _UNUSED_QUOTE_.mul(_SHARES_[msg.sender]).div(_TOTAL_SHARES_)); + } + + function claimLPToken() external onlyOwner afterFreeze { + IERC20(_POOL_).safeTransfer(_OWNER_, IERC20(_POOL_).balanceOf(address(this))); + } +} diff --git a/contracts/lib/DODOMath.sol b/contracts/lib/DODOMath.sol index 0a7ac42..c0b0962 100644 --- a/contracts/lib/DODOMath.sol +++ b/contracts/lib/DODOMath.sol @@ -42,6 +42,9 @@ library DODOMath { ) internal pure returns (uint256) { require(V0 > 0, "TARGET_IS_ZERO"); uint256 fairAmount = i.mul(V1.sub(V2)); // i*delta + if (k == 0) { + return fairAmount; + } uint256 V0V0V1V2 = DecimalMath.divFloor(V0.mul(V0).div(V1), V2); uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2) return DecimalMath.ONE.sub(k).add(penalty).mul(fairAmount).div(DecimalMath.ONE2); @@ -117,7 +120,7 @@ library DODOMath { } if (k == 0) { - return DecimalMath.mulFloor(i, delta); + return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta); } if (k == DecimalMath.ONE) {