diff --git a/contracts/DODOStablePool/impl/DSP.sol b/contracts/DODOStablePool/impl/DSP.sol new file mode 100644 index 0000000..502bad3 --- /dev/null +++ b/contracts/DODOStablePool/impl/DSP.sol @@ -0,0 +1,96 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {IFeeRateModel} from "../../lib/FeeRateModel.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {DSPTrader} from "./DSPTrader.sol"; +import {DSPFunding} from "./DSPFunding.sol"; +import {DSPVault} from "./DSPVault.sol"; + +/** + * @title DODO StablePool + * @author DODO Breeder + * + * @notice DODOStablePool initialization + */ +contract DSP is DSPTrader, DSPFunding { + function init( + address maintainer, + address baseTokenAddress, + address quoteTokenAddress, + uint256 lpFeeRate, + address mtFeeRateModel, + uint256 i, + uint256 k, + bool isOpenTWAP + ) external { + require(!_DSP_INITIALIZED_, "DSP_INITIALIZED"); + _DSP_INITIALIZED_ = true; + + require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME"); + _BASE_TOKEN_ = IERC20(baseTokenAddress); + _QUOTE_TOKEN_ = IERC20(quoteTokenAddress); + + require(i > 0 && i <= 10**36); + _I_ = i; + + require(k <= 10**18); + _K_ = k; + + _LP_FEE_RATE_ = lpFeeRate; + _MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel); + _MAINTAINER_ = maintainer; + + _IS_OPEN_TWAP_ = isOpenTWAP; + if (isOpenTWAP) _BLOCK_TIMESTAMP_LAST_ = uint32(block.timestamp % 2**32); + + string memory connect = "_"; + string memory suffix = "DLP"; + + name = string(abi.encodePacked(suffix, connect, addressToShortString(address(this)))); + symbol = "DLP"; + decimals = _BASE_TOKEN_.decimals(); + + // ============================== Permit ==================================== + uint256 chainId; + assembly { + chainId := chainid() + } + DOMAIN_SEPARATOR = keccak256( + abi.encode( + // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, + keccak256(bytes(name)), + keccak256(bytes("1")), + chainId, + address(this) + ) + ); + // ========================================================================== + } + + function addressToShortString(address _addr) public pure returns (string memory) { + bytes32 value = bytes32(uint256(_addr)); + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(8); + for (uint256 i = 0; i < 4; i++) { + str[i * 2] = alphabet[uint8(value[i + 12] >> 4)]; + str[1 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)]; + } + return string(str); + } + + // ============ Version Control ============ + + function version() external pure returns (string memory) { + return "DSP 1.0.0"; + } +} diff --git a/contracts/DODOStablePool/impl/DSPFunding.sol b/contracts/DODOStablePool/impl/DSPFunding.sol new file mode 100644 index 0000000..9c1c47b --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPFunding.sol @@ -0,0 +1,112 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {DSPVault} from "./DSPVault.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {IDODOCallee} from "../../intf/IDODOCallee.sol"; + +contract DSPFunding is DSPVault { + // ============ Events ============ + + event BuyShares(address to, uint256 increaseShares, uint256 totalShares); + + event SellShares(address payer, address to, uint256 decreaseShares, uint256 totalShares); + + // ============ Buy & Sell Shares ============ + + // buy shares [round down] + function buyShares(address to) + external + preventReentrant + returns ( + uint256 shares, + uint256 baseInput, + uint256 quoteInput + ) + { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + uint256 baseReserve = _BASE_RESERVE_; + uint256 quoteReserve = _QUOTE_RESERVE_; + + baseInput = baseBalance.sub(baseReserve); + quoteInput = quoteBalance.sub(quoteReserve); + require(baseInput > 0, "NO_BASE_INPUT"); + + // Round down when withdrawing. Therefore, never be a situation occuring balance is 0 but totalsupply is not 0 + // But May Happen,reserve >0 But totalSupply = 0 + if (totalSupply == 0) { + // case 1. initial supply + shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_) + ? DecimalMath.divFloor(quoteBalance, _I_) + : baseBalance; + _BASE_TARGET_ = uint112(shares); + _QUOTE_TARGET_ = uint112(DecimalMath.mulFloor(shares, _I_)); + } else if (baseReserve > 0 && quoteReserve > 0) { + // case 2. normal case + uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve); + uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve); + uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio; + shares = DecimalMath.mulFloor(totalSupply, mintRatio); + + _BASE_TARGET_ = uint112(uint256(_BASE_TARGET_).add(DecimalMath.mulFloor(uint256(_BASE_TARGET_), mintRatio))); + _QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_).add(DecimalMath.mulFloor(uint256(_QUOTE_TARGET_), mintRatio))); + } + _mint(to, shares); + _setReserve(baseBalance, quoteBalance); + emit BuyShares(to, shares, _SHARES_[to]); + } + + // sell shares [round down] + function sellShares( + uint256 shareAmount, + address to, + uint256 baseMinAmount, + uint256 quoteMinAmount, + bytes calldata data, + uint256 deadline + ) external preventReentrant returns (uint256 baseAmount, uint256 quoteAmount) { + require(deadline >= block.timestamp, "TIME_EXPIRED"); + require(shareAmount <= _SHARES_[msg.sender], "DLP_NOT_ENOUGH"); + + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + uint256 totalShares = totalSupply; + + baseAmount = baseBalance.mul(shareAmount).div(totalShares); + quoteAmount = quoteBalance.mul(shareAmount).div(totalShares); + + _BASE_TARGET_ = uint112(uint256(_BASE_TARGET_).sub(uint256(_BASE_TARGET_).mul(shareAmount).divCeil(totalShares))); + _QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_).sub(uint256(_QUOTE_TARGET_).mul(shareAmount).divCeil(totalShares))); + + require( + baseAmount >= baseMinAmount && quoteAmount >= quoteMinAmount, + "WITHDRAW_NOT_ENOUGH" + ); + + _burn(msg.sender, shareAmount); + _transferBaseOut(to, baseAmount); + _transferQuoteOut(to, quoteAmount); + _sync(); + + if (data.length > 0) { + //Same as DVM + IDODOCallee(to).DVMSellShareCall( + msg.sender, + shareAmount, + baseAmount, + quoteAmount, + data + ); + } + + emit SellShares(msg.sender, to, shareAmount, _SHARES_[msg.sender]); + } +} diff --git a/contracts/DODOStablePool/impl/DSPStorage.sol b/contracts/DODOStablePool/impl/DSPStorage.sol new file mode 100644 index 0000000..96afb2e --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPStorage.sol @@ -0,0 +1,107 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {DODOMath} from "../../lib/DODOMath.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {IFeeRateModel} from "../../lib/FeeRateModel.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {PMMPricing} from "../../lib/PMMPricing.sol"; + +contract DSPStorage is ReentrancyGuard { + using SafeMath for uint256; + + bool internal _DSP_INITIALIZED_; + bool public _IS_OPEN_TWAP_ = false; + + // ============ Core Address ============ + + address public _MAINTAINER_; + + IERC20 public _BASE_TOKEN_; + IERC20 public _QUOTE_TOKEN_; + + uint112 public _BASE_RESERVE_; + uint112 public _QUOTE_RESERVE_; + uint32 public _BLOCK_TIMESTAMP_LAST_; + + uint256 public _BASE_PRICE_CUMULATIVE_LAST_; + + uint112 public _BASE_TARGET_; + uint112 public _QUOTE_TARGET_; + uint32 public _RState_; + + // ============ Shares (ERC20) ============ + + string public symbol; + uint8 public decimals; + string public name; + + uint256 public totalSupply; + mapping(address => uint256) internal _SHARES_; + mapping(address => mapping(address => uint256)) internal _ALLOWED_; + + // ================= Permit ====================== + + bytes32 public DOMAIN_SEPARATOR; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + mapping(address => uint256) public nonces; + + // ============ Variables for Pricing ============ + + IFeeRateModel public _MT_FEE_RATE_MODEL_; + + uint256 public _LP_FEE_RATE_; + uint256 public _K_; + uint256 public _I_; + + // ============ Helper Functions ============ + + function getPMMState() public view returns (PMMPricing.PMMState memory state) { + state.i = _I_; + state.K = _K_; + state.B = _BASE_RESERVE_; + state.Q = _QUOTE_RESERVE_; + state.B0 = _BASE_TARGET_; // will be calculated in adjustedTarget + state.Q0 = _QUOTE_TARGET_; + state.R = PMMPricing.RState(_RState_); + PMMPricing.adjustedTarget(state); + } + + function getPMMStateForCall() + external + view + returns ( + uint256 i, + uint256 K, + uint256 B, + uint256 Q, + uint256 B0, + uint256 Q0, + uint256 R + ) + { + PMMPricing.PMMState memory state = getPMMState(); + i = state.i; + K = state.K; + B = state.B; + Q = state.Q; + B0 = state.B0; + Q0 = state.Q0; + R = uint256(state.R); + } + + function getMidPrice() public view returns (uint256 midPrice) { + return PMMPricing.getMidPrice(getPMMState()); + } +} diff --git a/contracts/DODOStablePool/impl/DSPTrader.sol b/contracts/DODOStablePool/impl/DSPTrader.sol new file mode 100644 index 0000000..82a832e --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPTrader.sol @@ -0,0 +1,237 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {DSPVault} from "./DSPVault.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {PMMPricing} from "../../lib/PMMPricing.sol"; +import {IDODOCallee} from "../../intf/IDODOCallee.sol"; + +contract DSPTrader is DSPVault { + using SafeMath for uint256; + + // ============ Events ============ + + event DODOSwap( + address fromToken, + address toToken, + uint256 fromAmount, + uint256 toAmount, + address trader, + address receiver + ); + + event DODOFlashLoan(address borrower, address assetTo, uint256 baseAmount, uint256 quoteAmount); + + event RChange(PMMPricing.RState newRState); + + // ============ Trade Functions ============ + + function sellBase(address to) external preventReentrant returns (uint256 receiveQuoteAmount) { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_)); + uint256 mtFee; + uint256 newBaseTarget; + PMMPricing.RState newRState; + (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput); + + _transferQuoteOut(to, receiveQuoteAmount); + _transferQuoteOut(_MAINTAINER_, mtFee); + + // update TARGET + if (_RState_ != uint32(newRState)) { + require(newBaseTarget <= uint112(-1), "OVERFLOW"); + _BASE_TARGET_ = uint112(newBaseTarget); + _RState_ = uint32(newRState); + emit RChange(newRState); + } + + _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); + + emit DODOSwap( + address(_BASE_TOKEN_), + address(_QUOTE_TOKEN_), + baseInput, + receiveQuoteAmount, + msg.sender, + to + ); + } + + function sellQuote(address to) external preventReentrant returns (uint256 receiveBaseAmount) { + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_)); + uint256 mtFee; + uint256 newQuoteTarget; + PMMPricing.RState newRState; + (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote( + tx.origin, + quoteInput + ); + + _transferBaseOut(to, receiveBaseAmount); + _transferBaseOut(_MAINTAINER_, mtFee); + + // update TARGET + if (_RState_ != uint32(newRState)) { + require(newQuoteTarget <= uint112(-1), "OVERFLOW"); + _QUOTE_TARGET_ = uint112(newQuoteTarget); + _RState_ = uint32(newRState); + emit RChange(newRState); + } + + _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); + + emit DODOSwap( + address(_QUOTE_TOKEN_), + address(_BASE_TOKEN_), + quoteInput, + receiveBaseAmount, + msg.sender, + to + ); + } + + function flashLoan( + uint256 baseAmount, + uint256 quoteAmount, + address assetTo, + bytes calldata data + ) external preventReentrant { + _transferBaseOut(assetTo, baseAmount); + _transferQuoteOut(assetTo, quoteAmount); + + if (data.length > 0) + IDODOCallee(assetTo).DSPFlashLoanCall(msg.sender, baseAmount, quoteAmount, data); + + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + + // no input -> pure loss + require( + baseBalance >= _BASE_RESERVE_ || quoteBalance >= _QUOTE_RESERVE_, + "FLASH_LOAN_FAILED" + ); + + // sell quote case + // quote input + base output + if (baseBalance < _BASE_RESERVE_) { + uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_)); + ( + uint256 receiveBaseAmount, + uint256 mtFee, + PMMPricing.RState newRState, + uint256 newQuoteTarget + ) = querySellQuote(tx.origin, quoteInput); // revert if quoteBalance 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) { + _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed; + } + _BLOCK_TIMESTAMP_LAST_ = blockTimestamp; + } + + // ============ Set States ============ + + function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal { + require(baseReserve <= uint112(-1) && quoteReserve <= uint112(-1), "OVERFLOW"); + _BASE_RESERVE_ = uint112(baseReserve); + _QUOTE_RESERVE_ = uint112(quoteReserve); + + if (_IS_OPEN_TWAP_) _twapUpdate(); + } + + function _sync() internal { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW"); + if (baseBalance != _BASE_RESERVE_) { + _BASE_RESERVE_ = uint112(baseBalance); + } + if (quoteBalance != _QUOTE_RESERVE_) { + _QUOTE_RESERVE_ = uint112(quoteBalance); + } + + if (_IS_OPEN_TWAP_) _twapUpdate(); + } + + function sync() external preventReentrant { + _sync(); + } + + // ============ 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); + } + } + + // ============ Shares (ERC20) ============ + + /** + * @dev transfer token for a specified address + * @param to The address to transfer to. + * @param amount The amount to be transferred. + */ + function transfer(address to, uint256 amount) public returns (bool) { + require(amount <= _SHARES_[msg.sender], "BALANCE_NOT_ENOUGH"); + + _SHARES_[msg.sender] = _SHARES_[msg.sender].sub(amount); + _SHARES_[to] = _SHARES_[to].add(amount); + emit Transfer(msg.sender, to, amount); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param owner The address to query the the balance of. + * @return balance An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address owner) external view returns (uint256 balance) { + return _SHARES_[owner]; + } + + /** + * @dev Transfer tokens from one address to another + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 the amount of tokens to be transferred + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public returns (bool) { + require(amount <= _SHARES_[from], "BALANCE_NOT_ENOUGH"); + require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); + + _SHARES_[from] = _SHARES_[from].sub(amount); + _SHARES_[to] = _SHARES_[to].add(amount); + _ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount); + emit Transfer(from, to, amount); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * @param spender The address which will spend the funds. + * @param amount The amount of tokens to be spent. + */ + function approve(address spender, uint256 amount) public returns (bool) { + _approve(msg.sender, spender, amount); + return true; + } + + function _approve( + address owner, + address spender, + uint256 amount + ) private { + _ALLOWED_[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Function to check the amount of tokens that an owner _ALLOWED_ to a spender. + * @param owner address The address which owns the funds. + * @param spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance(address owner, address spender) public view returns (uint256) { + return _ALLOWED_[owner][spender]; + } + + function _mint(address user, uint256 value) internal { + require(value > 1000, "MINT_AMOUNT_NOT_ENOUGH"); + _SHARES_[user] = _SHARES_[user].add(value); + totalSupply = totalSupply.add(value); + emit Mint(user, value); + emit Transfer(address(0), user, value); + } + + function _burn(address user, uint256 value) internal { + _SHARES_[user] = _SHARES_[user].sub(value); + totalSupply = totalSupply.sub(value); + emit Burn(user, value); + emit Transfer(user, address(0), value); + } + + // ============================ Permit ====================================== + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(deadline >= block.timestamp, "DODO_DSP_LP: EXPIRED"); + bytes32 digest = + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + require( + recoveredAddress != address(0) && recoveredAddress == owner, + "DODO_DSP_LP: INVALID_SIGNATURE" + ); + _approve(owner, spender, value); + } +} diff --git a/contracts/DODOStablePool/intf/IDSP.sol b/contracts/DODOStablePool/intf/IDSP.sol new file mode 100644 index 0000000..0cfc8bf --- /dev/null +++ b/contracts/DODOStablePool/intf/IDSP.sol @@ -0,0 +1,36 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +interface IDSP { + function init( + address maintainer, + address baseTokenAddress, + address quoteTokenAddress, + uint256 lpFeeRate, + address mtFeeRateModel, + uint256 i, + uint256 k, + bool isOpenTWAP + ) external; + + function _BASE_TOKEN_() external returns (address); + + function _QUOTE_TOKEN_() external returns (address); + + function _MT_FEE_RATE_MODEL_() external returns (address); + + function getVaultReserve() external returns (uint256 baseReserve, uint256 quoteReserve); + + function sellBase(address to) external returns (uint256); + + function sellQuote(address to) external returns (uint256); + + function buyShares(address to) external returns (uint256); +} diff --git a/contracts/Factory/DSPFactory.sol b/contracts/Factory/DSPFactory.sol new file mode 100644 index 0000000..a4e3348 --- /dev/null +++ b/contracts/Factory/DSPFactory.sol @@ -0,0 +1,158 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {ICloneFactory} from "../lib/CloneFactory.sol"; +import {IDSP} from "../DODOStablePool/intf/IDSP.sol"; + +interface IDSPFactory { + function createDODOStablePool( + address baseToken, + address quoteToken, + uint256 lpFeeRate, + uint256 i, + uint256 k, + bool isOpenTWAP + ) external returns (address newStablePool); +} + +/** + * @title DODO StablePool Factory + * @author DODO Breeder + * + * @notice Create And Register DSP Pools + */ +contract DSPFactory is InitializableOwnable { + // ============ Templates ============ + + address public immutable _CLONE_FACTORY_; + address public immutable _DEFAULT_MAINTAINER_; + address public immutable _DEFAULT_MT_FEE_RATE_MODEL_; + address public _DSP_TEMPLATE_; + + // ============ Registry ============ + + // base -> quote -> DSP address list + mapping(address => mapping(address => address[])) public _REGISTRY_; + // creator -> DSP address list + mapping(address => address[]) public _USER_REGISTRY_; + + // ============ Events ============ + + event NewDSP(address baseToken, address quoteToken, address creator, address DSP); + + event RemoveDSP(address DSP); + + // ============ Functions ============ + + constructor( + address cloneFactory, + address DSPTemplate, + address defaultMaintainer, + address defaultMtFeeRateModel + ) public { + _CLONE_FACTORY_ = cloneFactory; + _DSP_TEMPLATE_ = DSPTemplate; + _DEFAULT_MAINTAINER_ = defaultMaintainer; + _DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel; + } + + function createDODOStablePool( + address baseToken, + address quoteToken, + uint256 lpFeeRate, + uint256 i, + uint256 k, + bool isOpenTWAP + ) external returns (address newStablePool) { + newStablePool = ICloneFactory(_CLONE_FACTORY_).clone(_DSP_TEMPLATE_); + { + IDSP(newStablePool).init( + _DEFAULT_MAINTAINER_, + baseToken, + quoteToken, + lpFeeRate, + _DEFAULT_MT_FEE_RATE_MODEL_, + i, + k, + isOpenTWAP + ); + } + _REGISTRY_[baseToken][quoteToken].push(newStablePool); + _USER_REGISTRY_[tx.origin].push(newStablePool); + emit NewDSP(baseToken, quoteToken, tx.origin, newStablePool); + } + + // ============ Admin Operation Functions ============ + + function updateDSPTemplate(address _newDSPTemplate) external onlyOwner { + _DSP_TEMPLATE_ = _newDSPTemplate; + } + + function addPoolByAdmin( + address creator, + address baseToken, + address quoteToken, + address pool + ) external onlyOwner { + _REGISTRY_[baseToken][quoteToken].push(pool); + _USER_REGISTRY_[creator].push(pool); + emit NewDSP(baseToken, quoteToken, creator, pool); + } + + function removePoolByAdmin( + address creator, + address baseToken, + address quoteToken, + address pool + ) external onlyOwner { + address[] memory registryList = _REGISTRY_[baseToken][quoteToken]; + for (uint256 i = 0; i < registryList.length; i++) { + if (registryList[i] == pool) { + registryList[i] = registryList[registryList.length - 1]; + break; + } + } + _REGISTRY_[baseToken][quoteToken] = registryList; + _REGISTRY_[baseToken][quoteToken].pop(); + address[] memory userRegistryList = _USER_REGISTRY_[creator]; + for (uint256 i = 0; i < userRegistryList.length; i++) { + if (userRegistryList[i] == pool) { + userRegistryList[i] = userRegistryList[userRegistryList.length - 1]; + break; + } + } + _USER_REGISTRY_[creator] = userRegistryList; + _USER_REGISTRY_[creator].pop(); + emit RemoveDSP(pool); + } + + // ============ View Functions ============ + + function getDODOPool(address baseToken, address quoteToken) + external + view + returns (address[] memory machines) + { + return _REGISTRY_[baseToken][quoteToken]; + } + + function getDODOPoolBidirection(address token0, address token1) + external + view + returns (address[] memory baseToken0Machines, address[] memory baseToken1Machines) + { + return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]); + } + + function getDODOPoolByUser(address user) external view returns (address[] memory machines) { + return _USER_REGISTRY_[user]; + } +} diff --git a/contracts/intf/IDODOCallee.sol b/contracts/intf/IDODOCallee.sol index 7118809..a668917 100644 --- a/contracts/intf/IDODOCallee.sol +++ b/contracts/intf/IDODOCallee.sol @@ -31,6 +31,13 @@ interface IDODOCallee { bytes calldata data ) external; + function DSPFlashLoanCall( + address sender, + uint256 baseAmount, + uint256 quoteAmount, + bytes calldata data + ) external; + function CPCancelCall( address sender, uint256 amount,