├── v2 ├── registry │ └── registry.sol ├── IGainFactory.sol ├── IGainIL.sol ├── IGainYearnIRS.sol ├── IGainDelta.sol ├── IGainAAVEIRS.sol ├── TokenFactory.sol ├── proxy │ ├── IGainAAVEProxy.sol │ └── IGainYearnProxy.sol └── IGainBase.sol ├── CHANGELOG.md ├── README.md └── v1 ├── ImpermanentGain.sol └── IGWithSig.sol /v2/registry/registry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | contract registry { 6 | 7 | uint public totalsupply = 1; 8 | 9 | mapping(address => uint) public id; 10 | mapping(uint => address) public addr; 11 | 12 | function set() external { 13 | require(id[msg.sender] == 0, "already exist"); 14 | id[msg.sender] = totalsupply; 15 | addr[totalsupply] = msg.sender; 16 | totalsupply++; 17 | } 18 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## V1 to V2 transistion: 4 | 5 | - upgrade Solidity version from `0.5.17` to `0.8.7` 6 | - abstract template with basic functions 7 | - tokenize long/short position into ERC20 token 8 | - add `mintExactA/B` function for better UX 9 | - better token naming 10 | 11 | ## TokenFactory 12 | 13 | A factory that deploys [EIP1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy of mintable ERC20 token 14 | 15 | ## iGain V2 instances 16 | 17 | ### IL 18 | The V2 version of iGain V1, almost identical except for a configurable leverage 19 | 20 | ### AAVE IRS 21 | Tokenized incurred borrow interest of AAVE within a given period 22 | 23 | ### Yearn IRS 24 | Tokenized incurred yield of Yearn within a given period 25 | 26 | ### Delta 27 | Tokenized price movement by a sigmoid function 28 | -------------------------------------------------------------------------------- /v2/IGainFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | contract iGainFactory { 6 | address immutable template; 7 | 8 | address internal owner; 9 | 10 | address[] public terms; 11 | 12 | event NewTermCreated(address term); 13 | 14 | function getNumberOfIGains() external view returns (uint256) { 15 | return terms.length; 16 | } 17 | 18 | function newIGain() external returns (address igain) { 19 | require(msg.sender == owner); 20 | igain = createClone(template); 21 | terms.push(igain); 22 | emit NewTermCreated(igain); 23 | } 24 | 25 | function createClone(address target) internal returns (address result) { 26 | bytes20 targetBytes = bytes20(target); 27 | assembly { 28 | let clone := mload(0x40) 29 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 30 | mstore(add(clone, 0x14), targetBytes) 31 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 32 | result := create(0, clone, 0x37) 33 | } 34 | } 35 | 36 | constructor(address _template) { 37 | template = address(_template); 38 | owner = msg.sender; 39 | } 40 | 41 | function transferOwnership(address newOwner) public { 42 | require(msg.sender == owner); 43 | require(newOwner != address(0)); 44 | owner = newOwner; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /v2/IGainIL.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | import "./IGainBase.sol"; 6 | 7 | interface Oracle { 8 | function latestAnswer() external view returns (int256); 9 | } 10 | 11 | contract IGainIL is IGainBase { 12 | 13 | Oracle public oracle; 14 | 15 | uint256 public openPrice; 16 | uint256 public closePrice; 17 | uint256 public leverage; // in 1e18 18 | 19 | function init(address _baseToken, address _oracle, address _treasury, string calldata _batchName, uint256 _leverage, uint256 _duration, uint256 _a, uint256 _b) public { 20 | _init(_baseToken, _treasury, _batchName, _duration, _a, _b); 21 | oracle = Oracle(_oracle); 22 | leverage = _leverage; 23 | openPrice = uint256(oracle.latestAnswer()); 24 | } 25 | 26 | // can only call once after closeTime 27 | // get price from oracle and calculate IL 28 | function close() external override { 29 | require(_blockTimestamp() >= closeTime, "Not yet"); 30 | require(canBuy, "Closed"); 31 | canBuy = false; 32 | closePrice = uint256(oracle.latestAnswer()); 33 | 34 | uint256 ratio = openPrice * 1e18 / closePrice; 35 | uint256 _bPrice = calcIL(ratio) * leverage / 1e18; //leverage 36 | bPrice = _bPrice > 1e18 ? 1e18 : _bPrice; 37 | } 38 | 39 | function calcIL(uint256 ratio) public pure returns (uint256) { 40 | // 1 - sqrt(ratio) * 2 / (1 + ratio) 41 | return 1e18 - sqrt(ratio * 1e18) * 2e18 / (ratio + 1e18); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /v2/IGainYearnIRS.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | import "./IGainBase.sol"; 6 | 7 | interface IyVault { 8 | function pricePerShare() external view returns (uint256); 9 | } 10 | 11 | contract IGainYearnIRS is IGainBase { 12 | 13 | IyVault public vault; // yVault 14 | 15 | uint256 public initialRate; 16 | uint256 public endRate; 17 | uint256 public leverage; // in 1e18 18 | 19 | function init(address _baseToken, address _vault, address _treasury, string calldata _batchName, uint256 _leverage, uint256 _duration, uint256 _a, uint256 _b) public { 20 | _init(_baseToken, _treasury, _batchName, _duration, _a, _b); 21 | vault = IyVault(_vault); 22 | leverage = _leverage; 23 | initialRate = vault.pricePerShare(); 24 | require(initialRate > 0, "initialRate = 0"); 25 | } 26 | 27 | // 1 - swap fee (numerator, in 1e18 format) 28 | function fee() public override view returns (uint256) { 29 | uint256 time = _blockTimestamp(); 30 | uint256 _fee; 31 | if(time < closeTime) { 32 | _fee = maxFee - ( 33 | (time - openTime) * (maxFee - minFee) / (closeTime - openTime) 34 | ); 35 | } 36 | else { 37 | _fee = minFee; 38 | } 39 | return 1e18 - _fee; 40 | } 41 | 42 | function close() external override { 43 | require(_blockTimestamp() >= closeTime, "Not yet"); 44 | require(canBuy, "Closed"); 45 | canBuy = false; 46 | endRate = vault.pricePerShare(); 47 | 48 | if (endRate < initialRate) endRate = initialRate; // wierd cases prevention? 49 | 50 | uint256 ratio = (endRate - initialRate) * 1e18 / initialRate; 51 | uint256 _bPrice = ratio * leverage / 1e18; // leverage 52 | bPrice = _bPrice > 1e18 ? 1e18 : _bPrice; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /v2/IGainDelta.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | import "./IGainBase.sol"; 6 | 7 | interface Oracle { 8 | function latestAnswer() external view returns (int256); 9 | } 10 | 11 | contract IGainDelta is IGainBase { 12 | 13 | Oracle public oracle; 14 | 15 | uint256 public openPrice; 16 | uint256 public closePrice; 17 | uint256 public leverage; // in 1e18 18 | 19 | function init(address _baseToken, address _oracle, address _treasury, string calldata _batchName, uint256 _leverage, uint256 _duration, uint256 _a, uint256 _b) public { 20 | _init(_baseToken, _treasury, _batchName, _duration, _a, _b); 21 | oracle = Oracle(_oracle); 22 | leverage = _leverage; 23 | openPrice = uint256(oracle.latestAnswer()); 24 | } 25 | 26 | // 1 - swap fee (numerator, in 1e18 format) 27 | function fee() public override pure returns (uint256) { 28 | return 1e18 - minFee; 29 | } 30 | 31 | // can only call once after closeTime 32 | // get price from oracle and calculate IL 33 | function close() external override { 34 | require(_blockTimestamp() >= closeTime, "Not yet"); 35 | require(canBuy, "Closed"); 36 | canBuy = false; 37 | closePrice = uint256(oracle.latestAnswer()); 38 | 39 | bPrice = calcDelta(leverage, openPrice, closePrice); 40 | } 41 | 42 | // f(l, a, x) = l(x - a) / (2 * sqrt(a^2 + l^2 * (x - a)^2)) + 0.5 43 | function calcDelta(uint256 lever, uint256 anchor, uint256 index) public pure returns (uint256 delta) { 44 | uint256 numerator; 45 | uint256 denominator; 46 | if (index > anchor) { 47 | numerator = (index - anchor) * lever / 1e18; 48 | denominator = 2 * sqrt(anchor * anchor + numerator * numerator); 49 | delta = 0.5e18 + numerator * 1e18 / denominator; 50 | } 51 | else { 52 | numerator = (anchor - index) * lever / 1e18; 53 | denominator = 2 * sqrt(anchor * anchor + numerator * numerator); 54 | delta = 0.5e18 - numerator * 1e18 / denominator; 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /v2/IGainAAVEIRS.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | import "./IGainBase.sol"; 6 | 7 | interface ILendingPool { 8 | function getReserveNormalizedIncome(address asset) external view returns (uint256); 9 | function getReserveNormalizedVariableDebt(address asset) external view returns (uint256); 10 | } 11 | 12 | contract IGainAAVEIRS is IGainBase { 13 | 14 | ILendingPool public AAVE; // AAVE LendingPool 15 | address public asset; // underlying asset's address 16 | 17 | uint256 public initialRate; 18 | uint256 public endRate; 19 | uint256 public leverage; // in 1e18 20 | 21 | function init(address _baseToken, address _lendingPool, address _asset, address _treasury, string calldata _batchName, uint256 _leverage, uint256 _duration, uint256 _a, uint256 _b) public { 22 | _init(_baseToken, _treasury, _batchName, _duration, _a, _b); 23 | AAVE = ILendingPool(_lendingPool); 24 | asset = _asset; 25 | leverage = _leverage; 26 | initialRate = AAVE.getReserveNormalizedVariableDebt(asset); 27 | require(initialRate > 0, "initialRate = 0"); 28 | } 29 | 30 | // 1 - swap fee (numerator, in 1e18 format) 31 | function fee() public override view returns (uint256) { 32 | uint256 time = _blockTimestamp(); 33 | uint256 _fee; 34 | if(time < closeTime) { 35 | _fee = maxFee - ( 36 | (time - openTime) * (maxFee - minFee) / (closeTime - openTime) 37 | ); 38 | } 39 | else { 40 | _fee = minFee; 41 | } 42 | return 1e18 - _fee; 43 | } 44 | 45 | function close() external override { 46 | require(_blockTimestamp() >= closeTime, "Not yet"); 47 | require(canBuy, "Closed"); 48 | canBuy = false; 49 | endRate = AAVE.getReserveNormalizedVariableDebt(asset); 50 | 51 | if (endRate < initialRate) endRate = initialRate; // wierd cases prevention? 52 | 53 | uint256 ratio = (endRate - initialRate) * 1e18 / initialRate; 54 | uint256 _bPrice = ratio * leverage / 1e18; // leverage 55 | bPrice = _bPrice > 1e18 ? 1e18 : _bPrice; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IGainAAVEIRS.sol 2 | 3 | 4 | ## Introduction 5 | 6 | The settlement price of Long Token is determined by the deposit interest rate in the term period on AAVE platform. The more the interest accumulates, the higher (lower) the price of Long (Short) Token is. 7 | 8 | Purchasing Long Token means take a long position on future interest rate; in contrast, buying Short Token indicates holding a short position on future interest rate. 9 | 10 | ## Price Settlement 11 | 12 | `AAVE.getReserveNormalizedVariableDebt(asset)` 13 | Returns the normalized variable debt per unit of asset. 14 | 15 | This is the exchange rate of the tokenized variable debt and the value increases with the growth of the floating interest rate. 16 | 17 | 1. Save real-time exchange rate as initializing smart contract 18 | ``` 19 | initialRate = AAVE.getReserveNormalizedVariableDebt(asset) 20 | ``` 21 | 22 | 2. Record real-time exchange rate on settlement day 23 | ``` 24 | endRate = AAVE.getReserveNormalizedVariableDebt(asset); 25 | ``` 26 | 27 | 3. With `initialRate` and `endRate`, the growth of the floating deposit interest rate in the period can be calculated with percernt form `ratio`: 28 | 29 | ``` 30 | ratio = (endRate - initialRate) / initialRate 31 | ``` 32 | For example, if the ratio on the expiry day is 0.04, then it can be deduced that the interest rate is 4%. Settled with 4% interest rate, the Long Token is priced as $0.04 while the Short Token should be $0.96. 33 | 34 | 4. To increase capital efficiency, adopt appropriate leverage according to backtesting history data 35 | 36 | As above stated, if with 10x leverage, then the Long Token is settled with the price of $0.4 while the Short Token should be $0.6. 37 | ``` 38 | _bPrice = leverage * rate 39 | ``` 40 | 41 | Long is settled as bPrice 42 | 43 | ``` 44 | bPrice = min(_bPrice, 1) # Max. Price of Long is $1 45 | ``` 46 | 47 | Shoer = 1 - bPrice 48 | 49 | 50 | 51 | --- 52 | 53 | # IGainYearnIRS.sol 54 | ## Introduction 55 | 56 | The mechanism and calculation of IGainYearnIRS.sol is exactly the same as IGainAAVEIRS, and the only different part is to replace AAVE interest rate with Yearn interest rate. 57 | 58 | -> `AAVE.getReserveNormalizedVariableDebt(asset)` replaces with`vault.pricePerShare()` 59 | 60 | 61 | --- 62 | 63 | # IGainDelta.sol 64 | ## Introduction 65 | 66 | The mechanism of IGainDelta.sol is similar to the binary option in crypto price, but the payoff of the Long Token does not comply with all-or-none law. The settled price of Long Token will gradually go up from $0 to $1. As the leverage is larger, the liquidity is becoming more intensive. 67 | 68 | ![](https://i.imgur.com/sokYzNa.png) 69 | 70 | 71 | ## Price Settlement 72 | 73 | 74 | ``` 75 | y:Settled Price of Long Token, bPrice = calcDelta() 76 | x:Crypto Price on Settlement Date, closePrice 77 | a:Price on initialization, anchor = openPrice 78 | l:Leverage, lever 79 | ``` 80 | 81 | As the leverage (l) is larger, the horizontal compression occurs and the function's base graph is shrunk along the x-axis. Therefore, the liquidity values move in closer and closer to the centre, which is known as the starting price. In addition, to the same settlement price, the difference between the centre and the strike price will be inversely proportional to the leverage (l). 82 | 83 | As l = ∞, the curve is a step function. 84 | ![](https://i.imgur.com/kBJr1Hd.png) 85 | 86 | -------------------------------------------------------------------------------- /v2/TokenFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * @dev Provides information about the current execution context, including the 7 | * sender of the transaction and its data. While these are generally available 8 | * via msg.sender and msg.data, they should not be accessed in such a direct 9 | * manner, since when dealing with meta-transactions the account sending and 10 | * paying for execution may not be the actual sender (as far as an application 11 | * is concerned). 12 | * 13 | * This contract is only required for intermediate, library-like contracts. 14 | */ 15 | abstract contract Context { 16 | function _msgSender() internal view virtual returns (address) { 17 | return msg.sender; 18 | } 19 | 20 | function _msgData() internal view virtual returns (bytes calldata) { 21 | return msg.data; 22 | } 23 | } 24 | 25 | /** 26 | * @dev Interface of the ERC20 standard as defined in the EIP. 27 | */ 28 | interface IERC20 { 29 | /** 30 | * @dev Returns the amount of tokens in existence. 31 | */ 32 | function totalSupply() external view returns (uint256); 33 | 34 | /** 35 | * @dev Returns the amount of tokens owned by `account`. 36 | */ 37 | function balanceOf(address account) external view returns (uint256); 38 | 39 | /** 40 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 41 | * 42 | * Returns a boolean value indicating whether the operation succeeded. 43 | * 44 | * Emits a {Transfer} event. 45 | */ 46 | function transfer(address recipient, uint256 amount) external returns (bool); 47 | 48 | /** 49 | * @dev Returns the remaining number of tokens that `spender` will be 50 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 51 | * zero by default. 52 | * 53 | * This value changes when {approve} or {transferFrom} are called. 54 | */ 55 | function allowance(address owner, address spender) external view returns (uint256); 56 | 57 | /** 58 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 59 | * 60 | * Returns a boolean value indicating whether the operation succeeded. 61 | * 62 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 63 | * that someone may use both the old and the new allowance by unfortunate 64 | * transaction ordering. One possible solution to mitigate this race 65 | * condition is to first reduce the spender's allowance to 0 and set the 66 | * desired value afterwards: 67 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 68 | * 69 | * Emits an {Approval} event. 70 | */ 71 | function approve(address spender, uint256 amount) external returns (bool); 72 | 73 | /** 74 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 75 | * allowance mechanism. `amount` is then deducted from the caller's 76 | * allowance. 77 | * 78 | * Returns a boolean value indicating whether the operation succeeded. 79 | * 80 | * Emits a {Transfer} event. 81 | */ 82 | function transferFrom( 83 | address sender, 84 | address recipient, 85 | uint256 amount 86 | ) external returns (bool); 87 | 88 | /** 89 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 90 | * another (`to`). 91 | * 92 | * Note that `value` may be zero. 93 | */ 94 | event Transfer(address indexed from, address indexed to, uint256 value); 95 | 96 | /** 97 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 98 | * a call to {approve}. `value` is the new allowance. 99 | */ 100 | event Approval(address indexed owner, address indexed spender, uint256 value); 101 | } 102 | 103 | /** 104 | * @dev Implementation of the {IERC20} interface. 105 | */ 106 | contract ERC20 is Context, IERC20 { 107 | mapping(address => uint256) internal _balances; 108 | 109 | mapping(address => mapping(address => uint256)) internal _allowances; 110 | 111 | uint256 internal _totalSupply; 112 | 113 | string public name; 114 | string public symbol; 115 | uint8 public decimals; 116 | 117 | /** 118 | * @dev See {IERC20-totalSupply}. 119 | */ 120 | function totalSupply() public view virtual override returns (uint256) { 121 | return _totalSupply; 122 | } 123 | 124 | /** 125 | * @dev See {IERC20-balanceOf}. 126 | */ 127 | function balanceOf(address account) public view virtual override returns (uint256) { 128 | return _balances[account]; 129 | } 130 | 131 | /** 132 | * @dev See {IERC20-transfer}. 133 | * 134 | * Requirements: 135 | * 136 | * - `recipient` cannot be the zero address. 137 | * - the caller must have a balance of at least `amount`. 138 | */ 139 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 140 | _transfer(_msgSender(), recipient, amount); 141 | return true; 142 | } 143 | 144 | /** 145 | * @dev See {IERC20-allowance}. 146 | */ 147 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 148 | return _allowances[owner][spender]; 149 | } 150 | 151 | /** 152 | * @dev See {IERC20-approve}. 153 | * 154 | * Requirements: 155 | * 156 | * - `spender` cannot be the zero address. 157 | */ 158 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 159 | _approve(_msgSender(), spender, amount); 160 | return true; 161 | } 162 | 163 | /** 164 | * @dev See {IERC20-transferFrom}. 165 | * 166 | * Emits an {Approval} event indicating the updated allowance. This is not 167 | * required by the EIP. See the note at the beginning of {ERC20}. 168 | * 169 | * Requirements: 170 | * 171 | * - `sender` and `recipient` cannot be the zero address. 172 | * - `sender` must have a balance of at least `amount`. 173 | * - the caller must have allowance for ``sender``'s tokens of at least 174 | * `amount`. 175 | */ 176 | function transferFrom( 177 | address sender, 178 | address recipient, 179 | uint256 amount 180 | ) public virtual override returns (bool) { 181 | _transfer(sender, recipient, amount); 182 | 183 | uint256 currentAllowance = _allowances[sender][_msgSender()]; 184 | require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); 185 | unchecked { 186 | _approve(sender, _msgSender(), currentAllowance - amount); 187 | } 188 | 189 | return true; 190 | } 191 | 192 | /** 193 | * @dev Moves `amount` of tokens from `sender` to `recipient`. 194 | * 195 | * This internal function is equivalent to {transfer}, and can be used to 196 | * e.g. implement automatic token fees, slashing mechanisms, etc. 197 | * 198 | * Emits a {Transfer} event. 199 | * 200 | * Requirements: 201 | * 202 | * - `sender` cannot be the zero address. 203 | * - `recipient` cannot be the zero address. 204 | * - `sender` must have a balance of at least `amount`. 205 | */ 206 | function _transfer( 207 | address sender, 208 | address recipient, 209 | uint256 amount 210 | ) internal virtual { 211 | 212 | uint256 senderBalance = _balances[sender]; 213 | require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); 214 | unchecked { 215 | _balances[sender] = senderBalance - amount; 216 | } 217 | _balances[recipient] += amount; 218 | 219 | emit Transfer(sender, recipient, amount); 220 | } 221 | 222 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 223 | * the total supply. 224 | * 225 | * Emits a {Transfer} event with `from` set to the zero address. 226 | * 227 | * Requirements: 228 | * 229 | * - `account` cannot be the zero address. 230 | */ 231 | function _mint(address account, uint256 amount) internal virtual { 232 | require(account != address(0), "ERC20: mint to the zero address"); 233 | _totalSupply += amount; 234 | _balances[account] += amount; 235 | emit Transfer(address(0), account, amount); 236 | } 237 | 238 | /** 239 | * @dev Destroys `amount` tokens from `account`, reducing the 240 | * total supply. 241 | * 242 | * Emits a {Transfer} event with `to` set to the zero address. 243 | * 244 | * Requirements: 245 | * 246 | * - `account` cannot be the zero address. 247 | * - `account` must have at least `amount` tokens. 248 | */ 249 | function _burn(address account, uint256 amount) internal virtual { 250 | require(account != address(0), "ERC20: burn from the zero address"); 251 | uint256 accountBalance = _balances[account]; 252 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 253 | unchecked { 254 | _balances[account] = accountBalance - amount; 255 | } 256 | _totalSupply -= amount; 257 | emit Transfer(account, address(0), amount); 258 | } 259 | 260 | /** 261 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 262 | * 263 | * This internal function is equivalent to `approve`, and can be used to 264 | * e.g. set automatic allowances for certain subsystems, etc. 265 | * 266 | * Emits an {Approval} event. 267 | * 268 | * Requirements: 269 | * 270 | * - `owner` cannot be the zero address. 271 | * - `spender` cannot be the zero address. 272 | */ 273 | function _approve( 274 | address owner, 275 | address spender, 276 | uint256 amount 277 | ) internal virtual { 278 | require(owner != address(0), "ERC20: approve from the zero address"); 279 | require(spender != address(0), "ERC20: approve to the zero address"); 280 | 281 | _allowances[owner][spender] = amount; 282 | emit Approval(owner, spender, amount); 283 | } 284 | } 285 | 286 | contract ERC20Mintable is ERC20 { 287 | 288 | address internal owner; 289 | 290 | function mint(address to, uint256 amount) external { 291 | require(msg.sender == owner); 292 | _mint(to, amount); 293 | } 294 | 295 | function burn(address from, uint256 amount) external { 296 | require(msg.sender == owner); 297 | _burn(from, amount); 298 | } 299 | 300 | function init(address _owner, string calldata _name, string calldata _symbol, uint8 _decimals) external { 301 | require(owner == address(0)); 302 | owner = _owner; 303 | name = _name; 304 | symbol = _symbol; 305 | decimals = _decimals; 306 | } 307 | 308 | } 309 | 310 | contract TokenFactory { 311 | address immutable template; 312 | 313 | event NewTokenCreated(address owner, address token); 314 | 315 | function newToken(address _owner, string calldata _name, string calldata _symbol, uint8 _decimals) external returns (address token) { 316 | token = createClone(template); 317 | ERC20Mintable(token).init(_owner, _name, _symbol, _decimals); 318 | emit NewTokenCreated(_owner, token); 319 | } 320 | 321 | function createClone(address target) internal returns (address result) { 322 | bytes20 targetBytes = bytes20(target); 323 | assembly { 324 | let clone := mload(0x40) 325 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 326 | mstore(add(clone, 0x14), targetBytes) 327 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 328 | result := create(0, clone, 0x37) 329 | } 330 | } 331 | 332 | constructor() { 333 | ERC20Mintable instance = new ERC20Mintable(); 334 | template = address(instance); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /v2/proxy/IGainAAVEProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | interface IERC20 { 6 | function totalSupply() external view returns (uint); 7 | function balanceOf(address account) external view returns (uint); 8 | function transfer(address recipient, uint amount) external returns (bool); 9 | function allowance(address owner, address spender) external view returns (uint); 10 | function approve(address spender, uint amount) external returns (bool); 11 | function transferFrom(address sender, address recipient, uint amount) external returns (bool); 12 | event Transfer(address indexed from, address indexed to, uint value); 13 | event Approval(address indexed owner, address indexed spender, uint value); 14 | } 15 | 16 | library Address { 17 | /** 18 | * @dev Returns true if `account` is a contract. 19 | * 20 | * [IMPORTANT] 21 | * ==== 22 | * It is unsafe to assume that an address for which this function returns 23 | * false is an externally-owned account (EOA) and not a contract. 24 | * 25 | * Among others, `isContract` will return false for the following 26 | * types of addresses: 27 | * 28 | * - an externally-owned account 29 | * - a contract in construction 30 | * - an address where a contract will be created 31 | * - an address where a contract lived, but was destroyed 32 | * ==== 33 | */ 34 | function isContract(address account) internal view returns (bool) { 35 | // This method relies on extcodesize, which returns 0 for contracts in 36 | // construction, since the code is only stored at the end of the 37 | // constructor execution. 38 | 39 | uint256 size; 40 | assembly { 41 | size := extcodesize(account) 42 | } 43 | return size > 0; 44 | } 45 | 46 | /** 47 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 48 | * `recipient`, forwarding all available gas and reverting on errors. 49 | * 50 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 51 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 52 | * imposed by `transfer`, making them unable to receive funds via 53 | * `transfer`. {sendValue} removes this limitation. 54 | * 55 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 56 | * 57 | * IMPORTANT: because control is transferred to `recipient`, care must be 58 | * taken to not create reentrancy vulnerabilities. Consider using 59 | * {ReentrancyGuard} or the 60 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 61 | */ 62 | function sendValue(address payable recipient, uint256 amount) internal { 63 | require(address(this).balance >= amount, "Address: insufficient balance"); 64 | 65 | (bool success, ) = recipient.call{value: amount}(""); 66 | require(success, "Address: unable to send value, recipient may have reverted"); 67 | } 68 | 69 | /** 70 | * @dev Performs a Solidity function call using a low level `call`. A 71 | * plain `call` is an unsafe replacement for a function call: use this 72 | * function instead. 73 | * 74 | * If `target` reverts with a revert reason, it is bubbled up by this 75 | * function (like regular Solidity function calls). 76 | * 77 | * Returns the raw returned data. To convert to the expected return value, 78 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 79 | * 80 | * Requirements: 81 | * 82 | * - `target` must be a contract. 83 | * - calling `target` with `data` must not revert. 84 | * 85 | * _Available since v3.1._ 86 | */ 87 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 88 | return functionCall(target, data, "Address: low-level call failed"); 89 | } 90 | 91 | /** 92 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 93 | * `errorMessage` as a fallback revert reason when `target` reverts. 94 | * 95 | * _Available since v3.1._ 96 | */ 97 | function functionCall( 98 | address target, 99 | bytes memory data, 100 | string memory errorMessage 101 | ) internal returns (bytes memory) { 102 | return functionCallWithValue(target, data, 0, errorMessage); 103 | } 104 | 105 | /** 106 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 107 | * but also transferring `value` wei to `target`. 108 | * 109 | * Requirements: 110 | * 111 | * - the calling contract must have an ETH balance of at least `value`. 112 | * - the called Solidity function must be `payable`. 113 | * 114 | * _Available since v3.1._ 115 | */ 116 | function functionCallWithValue( 117 | address target, 118 | bytes memory data, 119 | uint256 value 120 | ) internal returns (bytes memory) { 121 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 122 | } 123 | 124 | /** 125 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 126 | * with `errorMessage` as a fallback revert reason when `target` reverts. 127 | * 128 | * _Available since v3.1._ 129 | */ 130 | function functionCallWithValue( 131 | address target, 132 | bytes memory data, 133 | uint256 value, 134 | string memory errorMessage 135 | ) internal returns (bytes memory) { 136 | require(address(this).balance >= value, "Address: insufficient balance for call"); 137 | require(isContract(target), "Address: call to non-contract"); 138 | 139 | (bool success, bytes memory returndata) = target.call{value: value}(data); 140 | return verifyCallResult(success, returndata, errorMessage); 141 | } 142 | 143 | /** 144 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 145 | * but performing a static call. 146 | * 147 | * _Available since v3.3._ 148 | */ 149 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 150 | return functionStaticCall(target, data, "Address: low-level static call failed"); 151 | } 152 | 153 | /** 154 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 155 | * but performing a static call. 156 | * 157 | * _Available since v3.3._ 158 | */ 159 | function functionStaticCall( 160 | address target, 161 | bytes memory data, 162 | string memory errorMessage 163 | ) internal view returns (bytes memory) { 164 | require(isContract(target), "Address: static call to non-contract"); 165 | 166 | (bool success, bytes memory returndata) = target.staticcall(data); 167 | return verifyCallResult(success, returndata, errorMessage); 168 | } 169 | 170 | /** 171 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 172 | * but performing a delegate call. 173 | * 174 | * _Available since v3.4._ 175 | */ 176 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 177 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 178 | } 179 | 180 | /** 181 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 182 | * but performing a delegate call. 183 | * 184 | * _Available since v3.4._ 185 | */ 186 | function functionDelegateCall( 187 | address target, 188 | bytes memory data, 189 | string memory errorMessage 190 | ) internal returns (bytes memory) { 191 | require(isContract(target), "Address: delegate call to non-contract"); 192 | 193 | (bool success, bytes memory returndata) = target.delegatecall(data); 194 | return verifyCallResult(success, returndata, errorMessage); 195 | } 196 | 197 | /** 198 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 199 | * revert reason using the provided one. 200 | * 201 | * _Available since v4.3._ 202 | */ 203 | function verifyCallResult( 204 | bool success, 205 | bytes memory returndata, 206 | string memory errorMessage 207 | ) internal pure returns (bytes memory) { 208 | if (success) { 209 | return returndata; 210 | } else { 211 | // Look for revert reason and bubble it up if present 212 | if (returndata.length > 0) { 213 | // The easiest way to bubble the revert reason is using memory via assembly 214 | 215 | assembly { 216 | let returndata_size := mload(returndata) 217 | revert(add(32, returndata), returndata_size) 218 | } 219 | } else { 220 | revert(errorMessage); 221 | } 222 | } 223 | } 224 | } 225 | 226 | library SafeERC20 { 227 | using Address for address; 228 | 229 | function safeTransfer( 230 | IERC20 token, 231 | address to, 232 | uint256 value 233 | ) internal { 234 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 235 | } 236 | 237 | function safeTransferFrom( 238 | IERC20 token, 239 | address from, 240 | address to, 241 | uint256 value 242 | ) internal { 243 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 244 | } 245 | 246 | /** 247 | * @dev Deprecated. This function has issues similar to the ones found in 248 | * {IERC20-approve}, and its usage is discouraged. 249 | * 250 | * Whenever possible, use {safeIncreaseAllowance} and 251 | * {safeDecreaseAllowance} instead. 252 | */ 253 | function safeApprove( 254 | IERC20 token, 255 | address spender, 256 | uint256 value 257 | ) internal { 258 | // safeApprove should only be called when setting an initial allowance, 259 | // or when resetting it to zero. To increase and decrease it, use 260 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 261 | require( 262 | (value == 0) || (token.allowance(address(this), spender) == 0), 263 | "SafeERC20: approve from non-zero to non-zero allowance" 264 | ); 265 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 266 | } 267 | 268 | function safeIncreaseAllowance( 269 | IERC20 token, 270 | address spender, 271 | uint256 value 272 | ) internal { 273 | uint256 newAllowance = token.allowance(address(this), spender) + value; 274 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 275 | } 276 | 277 | function safeDecreaseAllowance( 278 | IERC20 token, 279 | address spender, 280 | uint256 value 281 | ) internal { 282 | unchecked { 283 | uint256 oldAllowance = token.allowance(address(this), spender); 284 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 285 | uint256 newAllowance = oldAllowance - value; 286 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 287 | } 288 | } 289 | 290 | /** 291 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 292 | * on the return value: the return value is optional (but if data is returned, it must not be false). 293 | * @param token The token targeted by the call. 294 | * @param data The call data (encoded using abi.encode or one of its variants). 295 | */ 296 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 297 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 298 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 299 | // the target address contains contract code and also asserts for success in the low-level call. 300 | 301 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 302 | if (returndata.length > 0) { 303 | // Return data is optional 304 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 305 | } 306 | } 307 | } 308 | 309 | interface iGain { 310 | function mintA(uint256 amount, uint256 min_a) external returns (uint256 _a); 311 | function mintB(uint256 amount, uint256 min_b) external returns (uint256 _b); 312 | function mintLP(uint256 amount, uint256 min_lp) external returns (uint256 _lp); 313 | function burnA(uint256 _a, uint256 min_amount) external returns (uint256 amount); 314 | function burnB(uint256 _b, uint256 min_amount) external returns (uint256 amount); 315 | function burnLP(uint256 lp, uint256 min_amount) external returns (uint256 amount); 316 | function a() external returns (address A); 317 | function b() external returns (address B); 318 | } 319 | 320 | interface Pool { 321 | function stakeFor(address to, uint256 amount) external; 322 | } 323 | 324 | interface ILendingPool { 325 | function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external; 326 | function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external; 327 | } 328 | 329 | contract iGainAAVEProxy { 330 | using SafeERC20 for IERC20; 331 | 332 | event Approval(address indexed token, address spender); 333 | event Mint(address indexed igain, address indexed user, bytes32 indexed output, uint256 amountIn, uint256 amountOut); 334 | event Deposit(address indexed igain, address indexed user, uint256 amount); 335 | event Borrow(address indexed igain, address indexed user, uint256 amount); 336 | event Farm(address indexed igain, address indexed user, uint256 amount); 337 | 338 | function approve(IERC20 token, address spender) public { 339 | token.safeApprove(spender, type(uint256).max); 340 | emit Approval(address(token), spender); 341 | } 342 | 343 | function mintA(iGain igain, IERC20 token, uint256 amount, uint256 min_a) external returns (uint256 _a) { 344 | token.safeTransferFrom(msg.sender, address(this), amount); 345 | _a = igain.mintA(amount, min_a); 346 | IERC20(igain.a()).safeTransfer(msg.sender, _a); 347 | emit Mint(address(igain), msg.sender, "A", amount, _a); 348 | } 349 | 350 | function mintB(iGain igain, IERC20 token, uint256 amount, uint256 min_b) external returns (uint256 _b) { 351 | token.safeTransferFrom(msg.sender, address(this), amount); 352 | _b = igain.mintB(amount, min_b); 353 | IERC20(igain.b()).safeTransfer(msg.sender, _b); 354 | emit Mint(address(igain), msg.sender, "B", amount, _b); 355 | } 356 | 357 | function mintLP(iGain igain, IERC20 token, uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 358 | token.safeTransferFrom(msg.sender, address(this), amount); 359 | _lp = igain.mintLP(amount, min_lp); 360 | IERC20(address(igain)).safeTransfer(msg.sender, _lp); 361 | emit Mint(address(igain), msg.sender, "LP", amount, _lp); 362 | } 363 | 364 | function fixedDeposit(iGain igain, IERC20 token, ILendingPool lendingPool, uint256 depositAmount, uint256 igainAmount, uint256 minToken) external returns (uint256 _a) { 365 | token.safeTransferFrom(msg.sender, address(this), depositAmount + igainAmount); 366 | lendingPool.deposit(address(token), depositAmount, msg.sender, uint16(0)); 367 | _a = igain.mintA(igainAmount, minToken); 368 | IERC20(igain.a()).safeTransfer(msg.sender, _a); 369 | emit Mint(address(igain), msg.sender, "A", igainAmount, _a); 370 | emit Deposit(address(igain), msg.sender, depositAmount); 371 | } 372 | 373 | function fixedBorrow(iGain igain, IERC20 token, ILendingPool lendingPool, uint256 borrowAmount, uint256 igainAmount, uint256 minToken) external returns (uint256 _b) { 374 | lendingPool.borrow(address(token), borrowAmount, 2, uint16(0), msg.sender); 375 | _b = igain.mintB(igainAmount, minToken); 376 | token.safeTransfer(msg.sender, borrowAmount - igainAmount); 377 | IERC20(igain.b()).safeTransfer(msg.sender, _b); 378 | emit Mint(address(igain), msg.sender, "B", igainAmount, _b); 379 | emit Borrow(address(igain), msg.sender, borrowAmount); 380 | } 381 | 382 | function mintLPandFarm(iGain igain, Pool pool, IERC20 token, uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 383 | token.safeTransferFrom(msg.sender, address(this), amount); 384 | _lp = igain.mintLP(amount, min_lp); 385 | pool.stakeFor(msg.sender, _lp); 386 | emit Mint(address(igain), msg.sender, "LP", amount, _lp); 387 | emit Farm(address(igain), msg.sender, _lp); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /v2/proxy/IGainYearnProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | interface IERC20 { 6 | function totalSupply() external view returns (uint); 7 | function balanceOf(address account) external view returns (uint); 8 | function transfer(address recipient, uint amount) external returns (bool); 9 | function allowance(address owner, address spender) external view returns (uint); 10 | function approve(address spender, uint amount) external returns (bool); 11 | function transferFrom(address sender, address recipient, uint amount) external returns (bool); 12 | event Transfer(address indexed from, address indexed to, uint value); 13 | event Approval(address indexed owner, address indexed spender, uint value); 14 | } 15 | 16 | library Address { 17 | /** 18 | * @dev Returns true if `account` is a contract. 19 | * 20 | * [IMPORTANT] 21 | * ==== 22 | * It is unsafe to assume that an address for which this function returns 23 | * false is an externally-owned account (EOA) and not a contract. 24 | * 25 | * Among others, `isContract` will return false for the following 26 | * types of addresses: 27 | * 28 | * - an externally-owned account 29 | * - a contract in construction 30 | * - an address where a contract will be created 31 | * - an address where a contract lived, but was destroyed 32 | * ==== 33 | */ 34 | function isContract(address account) internal view returns (bool) { 35 | // This method relies on extcodesize, which returns 0 for contracts in 36 | // construction, since the code is only stored at the end of the 37 | // constructor execution. 38 | 39 | uint256 size; 40 | assembly { 41 | size := extcodesize(account) 42 | } 43 | return size > 0; 44 | } 45 | 46 | /** 47 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 48 | * `recipient`, forwarding all available gas and reverting on errors. 49 | * 50 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 51 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 52 | * imposed by `transfer`, making them unable to receive funds via 53 | * `transfer`. {sendValue} removes this limitation. 54 | * 55 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 56 | * 57 | * IMPORTANT: because control is transferred to `recipient`, care must be 58 | * taken to not create reentrancy vulnerabilities. Consider using 59 | * {ReentrancyGuard} or the 60 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 61 | */ 62 | function sendValue(address payable recipient, uint256 amount) internal { 63 | require(address(this).balance >= amount, "Address: insufficient balance"); 64 | 65 | (bool success, ) = recipient.call{value: amount}(""); 66 | require(success, "Address: unable to send value, recipient may have reverted"); 67 | } 68 | 69 | /** 70 | * @dev Performs a Solidity function call using a low level `call`. A 71 | * plain `call` is an unsafe replacement for a function call: use this 72 | * function instead. 73 | * 74 | * If `target` reverts with a revert reason, it is bubbled up by this 75 | * function (like regular Solidity function calls). 76 | * 77 | * Returns the raw returned data. To convert to the expected return value, 78 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 79 | * 80 | * Requirements: 81 | * 82 | * - `target` must be a contract. 83 | * - calling `target` with `data` must not revert. 84 | * 85 | * _Available since v3.1._ 86 | */ 87 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 88 | return functionCall(target, data, "Address: low-level call failed"); 89 | } 90 | 91 | /** 92 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 93 | * `errorMessage` as a fallback revert reason when `target` reverts. 94 | * 95 | * _Available since v3.1._ 96 | */ 97 | function functionCall( 98 | address target, 99 | bytes memory data, 100 | string memory errorMessage 101 | ) internal returns (bytes memory) { 102 | return functionCallWithValue(target, data, 0, errorMessage); 103 | } 104 | 105 | /** 106 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 107 | * but also transferring `value` wei to `target`. 108 | * 109 | * Requirements: 110 | * 111 | * - the calling contract must have an ETH balance of at least `value`. 112 | * - the called Solidity function must be `payable`. 113 | * 114 | * _Available since v3.1._ 115 | */ 116 | function functionCallWithValue( 117 | address target, 118 | bytes memory data, 119 | uint256 value 120 | ) internal returns (bytes memory) { 121 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 122 | } 123 | 124 | /** 125 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 126 | * with `errorMessage` as a fallback revert reason when `target` reverts. 127 | * 128 | * _Available since v3.1._ 129 | */ 130 | function functionCallWithValue( 131 | address target, 132 | bytes memory data, 133 | uint256 value, 134 | string memory errorMessage 135 | ) internal returns (bytes memory) { 136 | require(address(this).balance >= value, "Address: insufficient balance for call"); 137 | require(isContract(target), "Address: call to non-contract"); 138 | 139 | (bool success, bytes memory returndata) = target.call{value: value}(data); 140 | return verifyCallResult(success, returndata, errorMessage); 141 | } 142 | 143 | /** 144 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 145 | * but performing a static call. 146 | * 147 | * _Available since v3.3._ 148 | */ 149 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 150 | return functionStaticCall(target, data, "Address: low-level static call failed"); 151 | } 152 | 153 | /** 154 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 155 | * but performing a static call. 156 | * 157 | * _Available since v3.3._ 158 | */ 159 | function functionStaticCall( 160 | address target, 161 | bytes memory data, 162 | string memory errorMessage 163 | ) internal view returns (bytes memory) { 164 | require(isContract(target), "Address: static call to non-contract"); 165 | 166 | (bool success, bytes memory returndata) = target.staticcall(data); 167 | return verifyCallResult(success, returndata, errorMessage); 168 | } 169 | 170 | /** 171 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 172 | * but performing a delegate call. 173 | * 174 | * _Available since v3.4._ 175 | */ 176 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 177 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 178 | } 179 | 180 | /** 181 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 182 | * but performing a delegate call. 183 | * 184 | * _Available since v3.4._ 185 | */ 186 | function functionDelegateCall( 187 | address target, 188 | bytes memory data, 189 | string memory errorMessage 190 | ) internal returns (bytes memory) { 191 | require(isContract(target), "Address: delegate call to non-contract"); 192 | 193 | (bool success, bytes memory returndata) = target.delegatecall(data); 194 | return verifyCallResult(success, returndata, errorMessage); 195 | } 196 | 197 | /** 198 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 199 | * revert reason using the provided one. 200 | * 201 | * _Available since v4.3._ 202 | */ 203 | function verifyCallResult( 204 | bool success, 205 | bytes memory returndata, 206 | string memory errorMessage 207 | ) internal pure returns (bytes memory) { 208 | if (success) { 209 | return returndata; 210 | } else { 211 | // Look for revert reason and bubble it up if present 212 | if (returndata.length > 0) { 213 | // The easiest way to bubble the revert reason is using memory via assembly 214 | 215 | assembly { 216 | let returndata_size := mload(returndata) 217 | revert(add(32, returndata), returndata_size) 218 | } 219 | } else { 220 | revert(errorMessage); 221 | } 222 | } 223 | } 224 | } 225 | 226 | library SafeERC20 { 227 | using Address for address; 228 | 229 | function safeTransfer( 230 | IERC20 token, 231 | address to, 232 | uint256 value 233 | ) internal { 234 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 235 | } 236 | 237 | function safeTransferFrom( 238 | IERC20 token, 239 | address from, 240 | address to, 241 | uint256 value 242 | ) internal { 243 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 244 | } 245 | 246 | /** 247 | * @dev Deprecated. This function has issues similar to the ones found in 248 | * {IERC20-approve}, and its usage is discouraged. 249 | * 250 | * Whenever possible, use {safeIncreaseAllowance} and 251 | * {safeDecreaseAllowance} instead. 252 | */ 253 | function safeApprove( 254 | IERC20 token, 255 | address spender, 256 | uint256 value 257 | ) internal { 258 | // safeApprove should only be called when setting an initial allowance, 259 | // or when resetting it to zero. To increase and decrease it, use 260 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 261 | require( 262 | (value == 0) || (token.allowance(address(this), spender) == 0), 263 | "SafeERC20: approve from non-zero to non-zero allowance" 264 | ); 265 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 266 | } 267 | 268 | function safeIncreaseAllowance( 269 | IERC20 token, 270 | address spender, 271 | uint256 value 272 | ) internal { 273 | uint256 newAllowance = token.allowance(address(this), spender) + value; 274 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 275 | } 276 | 277 | function safeDecreaseAllowance( 278 | IERC20 token, 279 | address spender, 280 | uint256 value 281 | ) internal { 282 | unchecked { 283 | uint256 oldAllowance = token.allowance(address(this), spender); 284 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 285 | uint256 newAllowance = oldAllowance - value; 286 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 287 | } 288 | } 289 | 290 | /** 291 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 292 | * on the return value: the return value is optional (but if data is returned, it must not be false). 293 | * @param token The token targeted by the call. 294 | * @param data The call data (encoded using abi.encode or one of its variants). 295 | */ 296 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 297 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 298 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 299 | // the target address contains contract code and also asserts for success in the low-level call. 300 | 301 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 302 | if (returndata.length > 0) { 303 | // Return data is optional 304 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 305 | } 306 | } 307 | } 308 | 309 | interface iGain { 310 | function mintA(uint256 amount, uint256 min_a) external returns (uint256 _a); 311 | function mintB(uint256 amount, uint256 min_b) external returns (uint256 _b); 312 | function mintLP(uint256 amount, uint256 min_lp) external returns (uint256 _lp); 313 | function burnA(uint256 _a, uint256 min_amount) external returns (uint256 amount); 314 | function burnB(uint256 _b, uint256 min_amount) external returns (uint256 amount); 315 | function burnLP(uint256 lp, uint256 min_amount) external returns (uint256 amount); 316 | function a() external returns (address A); 317 | function b() external returns (address B); 318 | } 319 | 320 | interface Pool { 321 | function stakeFor(address to, uint256 amount) external; 322 | } 323 | 324 | interface IyVault is IERC20 { 325 | function pricePerShare() external view returns (uint256); 326 | function deposit(uint256 amount, address recipient) external returns (uint256); 327 | function withdraw(uint256 maxShare, address recipient) external returns (uint256); 328 | } 329 | 330 | contract iGainYearnProxy { 331 | using SafeERC20 for IERC20; 332 | 333 | event Approval(address indexed token, address spender); 334 | event Mint(address indexed igain, address indexed user, bytes32 indexed output, uint256 amountIn, uint256 amountOut); 335 | event Burn(address indexed igain, address indexed user, bytes32 indexed input, uint256 amountIn, uint256 amountOut); 336 | event Deposit(address indexed igain, address indexed user, uint256 amount); 337 | event Farm(address indexed igain, address indexed user, uint256 amount); 338 | 339 | function approve(IERC20 token, address spender) public { 340 | token.safeApprove(spender, type(uint256).max); 341 | emit Approval(address(token), spender); 342 | } 343 | 344 | function mintA(iGain igain, IERC20 token, IyVault vault, uint256 amount, uint256 min_a) external returns (uint256 _a) { 345 | token.safeTransferFrom(msg.sender, address(this), amount); 346 | uint256 yvAmount = vault.deposit(amount, address(this)); 347 | _a = igain.mintA(yvAmount, min_a); 348 | IERC20(igain.a()).safeTransfer(msg.sender, _a); 349 | emit Mint(address(igain), msg.sender, "A", amount, _a); 350 | } 351 | 352 | function mintB(iGain igain, IERC20 token, IyVault vault, uint256 amount, uint256 min_b) external returns (uint256 _b) { 353 | token.safeTransferFrom(msg.sender, address(this), amount); 354 | uint256 yvAmount = vault.deposit(amount, address(this)); 355 | _b = igain.mintB(yvAmount, min_b); 356 | IERC20(igain.b()).safeTransfer(msg.sender, _b); 357 | emit Mint(address(igain), msg.sender, "B", amount, _b); 358 | } 359 | 360 | function mintLP(iGain igain, IERC20 token, IyVault vault, uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 361 | token.safeTransferFrom(msg.sender, address(this), amount); 362 | uint256 yvAmount = vault.deposit(amount, address(this)); 363 | _lp = igain.mintLP(yvAmount, min_lp); 364 | IERC20(address(igain)).safeTransfer(msg.sender, _lp); 365 | emit Mint(address(igain), msg.sender, "LP", amount, _lp); 366 | } 367 | 368 | function burnA(iGain igain, IyVault vault, uint256 _a, uint256 min_amount) external returns (uint256 amount) { 369 | IERC20(igain.a()).safeTransferFrom(msg.sender, address(this), _a); 370 | uint256 yvAmount = igain.burnA(_a, 0); 371 | uint256 fee = yvAmount * 0.01e18 / 1e18; 372 | yvAmount -= fee; 373 | amount = vault.withdraw(yvAmount, msg.sender); 374 | require(amount >= min_amount, "Slippage"); 375 | emit Burn(address(igain), msg.sender, "A", _a, amount); 376 | } 377 | 378 | function burnB(iGain igain, IyVault vault, uint256 _b, uint256 min_amount) external returns (uint256 amount) { 379 | IERC20(igain.b()).safeTransferFrom(msg.sender, address(this), _b); 380 | uint256 yvAmount = igain.burnB(_b, 0); 381 | uint256 fee = yvAmount * 0.01e18 / 1e18; 382 | yvAmount -= fee; 383 | amount = vault.withdraw(yvAmount, msg.sender); 384 | require(amount >= min_amount, "Slippage"); 385 | emit Burn(address(igain), msg.sender, "B", _b, amount); 386 | } 387 | 388 | function burnLP(iGain igain, IyVault vault, uint256 _lp, uint256 min_amount) external returns (uint256 amount) { 389 | IERC20(address(igain)).safeTransferFrom(msg.sender, address(this), _lp); 390 | uint256 yvAmount = igain.burnLP(_lp, 0); 391 | uint256 fee = yvAmount * 0.01e18 / 1e18; 392 | yvAmount -= fee; 393 | amount = vault.withdraw(yvAmount, msg.sender); 394 | require(amount >= min_amount, "Slippage"); 395 | emit Burn(address(igain), msg.sender, "LP", _lp, amount); 396 | } 397 | 398 | function fixedDeposit(iGain igain, IERC20 token, IyVault vault, uint256 depositAmount, uint256 igainAmount, uint256 minToken) external returns (uint256 _a) { 399 | token.safeTransferFrom(msg.sender, address(this), depositAmount + igainAmount); 400 | vault.deposit(depositAmount, msg.sender); 401 | uint256 yvAmount = vault.deposit(igainAmount, address(this)); 402 | _a = igain.mintA(yvAmount, minToken); 403 | IERC20(igain.a()).safeTransfer(msg.sender, _a); 404 | emit Mint(address(igain), msg.sender, "A", igainAmount, _a); 405 | emit Deposit(address(igain), msg.sender, depositAmount); 406 | } 407 | 408 | function mintLPandFarm(iGain igain, Pool pool, IERC20 token, IyVault vault, uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 409 | token.safeTransferFrom(msg.sender, address(this), amount); 410 | uint256 yvAmount = vault.deposit(amount, address(this)); 411 | _lp = igain.mintLP(yvAmount, min_lp); 412 | pool.stakeFor(msg.sender, _lp); 413 | emit Mint(address(igain), msg.sender, "LP", amount, _lp); 414 | emit Farm(address(igain), msg.sender, _lp); 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /v1/ImpermanentGain.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | library SafeMath { 4 | 5 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 6 | if (a == 0) 7 | return 0; 8 | uint256 c = a * b; 9 | require(c / a == b); 10 | return c; 11 | } 12 | 13 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 14 | require(b > 0); 15 | uint256 c = a / b; 16 | return c; 17 | } 18 | 19 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 20 | require(b <= a); 21 | uint256 c = a - b; 22 | return c; 23 | } 24 | 25 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 26 | uint256 c = a + b; 27 | require(c >= a); 28 | return c; 29 | } 30 | 31 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 32 | require(b != 0); 33 | return a % b; 34 | } 35 | 36 | function sqrt(uint256 x) internal pure returns (uint256) { 37 | uint256 z = add(x >> 1, 1); 38 | uint256 y = x; 39 | while (z < y) 40 | { 41 | y = z; 42 | z = ((add((x / z), z)) / 2); 43 | } 44 | return y; 45 | } 46 | } 47 | 48 | contract ERC20 { 49 | using SafeMath for uint256; 50 | 51 | mapping (address => uint256) internal _balances; 52 | mapping (address => mapping (address => uint256)) internal _allowed; 53 | 54 | event Transfer(address indexed from, address indexed to, uint256 value); 55 | event Approval(address indexed owner, address indexed spender, uint256 value); 56 | 57 | uint256 internal _totalSupply; 58 | 59 | /** 60 | * @dev Total number of tokens in existence 61 | */ 62 | function totalSupply() public view returns (uint256) { 63 | return _totalSupply; 64 | } 65 | 66 | /** 67 | * @dev Gets the balance of the specified address. 68 | * @param owner The address to query the balance of. 69 | * @return A uint256 representing the amount owned by the passed address. 70 | */ 71 | function balanceOf(address owner) public view returns (uint256) { 72 | return _balances[owner]; 73 | } 74 | 75 | /** 76 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 77 | * @param owner address The address which owns the funds. 78 | * @param spender address The address which will spend the funds. 79 | * @return A uint256 specifying the amount of tokens still available for the spender. 80 | */ 81 | function allowance(address owner, address spender) public view returns (uint256) { 82 | return _allowed[owner][spender]; 83 | } 84 | 85 | /** 86 | * @dev Transfer token to a specified address 87 | * @param to The address to transfer to. 88 | * @param value The amount to be transferred. 89 | */ 90 | function transfer(address to, uint256 value) public returns (bool) { 91 | _transfer(msg.sender, to, value); 92 | return true; 93 | } 94 | 95 | /** 96 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 97 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 98 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 99 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 100 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 101 | * @param spender The address which will spend the funds. 102 | * @param value The amount of tokens to be spent. 103 | */ 104 | function approve(address spender, uint256 value) public returns (bool) { 105 | _allowed[msg.sender][spender] = value; 106 | emit Approval(msg.sender, spender, value); 107 | return true; 108 | } 109 | 110 | /** 111 | * @dev Transfer tokens from one address to another. 112 | * Note that while this function emits an Approval event, this is not required as per the specification, 113 | * and other compliant implementations may not emit the event. 114 | * @param from address The address which you want to send tokens from 115 | * @param to address The address which you want to transfer to 116 | * @param value uint256 the amount of tokens to be transferred 117 | */ 118 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 119 | if (from != msg.sender && _allowed[from][msg.sender] != uint256(-1)) 120 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 121 | _transfer(from, to, value); 122 | return true; 123 | } 124 | 125 | function _transfer(address from, address to, uint256 value) internal { 126 | require(to != address(0)); 127 | _balances[from] = _balances[from].sub(value); 128 | _balances[to] = _balances[to].add(value); 129 | emit Transfer(from, to, value); 130 | } 131 | 132 | } 133 | 134 | contract ERC20Mintable is ERC20 { 135 | string public name; 136 | string public symbol; 137 | uint8 public decimals; 138 | 139 | function _mint(address to, uint256 amount) internal { 140 | _balances[to] = _balances[to].add(amount); 141 | _totalSupply = _totalSupply.add(amount); 142 | emit Transfer(address(0), to, amount); 143 | } 144 | 145 | function _burn(address from, uint256 amount) internal { 146 | _balances[from] = _balances[from].sub(amount); 147 | _totalSupply = _totalSupply.sub(amount); 148 | emit Transfer(from, address(0), amount); 149 | } 150 | } 151 | 152 | interface IERC20 { 153 | function totalSupply() external view returns (uint); 154 | function balanceOf(address account) external view returns (uint); 155 | function transfer(address recipient, uint amount) external returns (bool); 156 | function allowance(address owner, address spender) external view returns (uint); 157 | function approve(address spender, uint amount) external returns (bool); 158 | function transferFrom(address sender, address recipient, uint amount) external returns (bool); 159 | event Transfer(address indexed from, address indexed to, uint value); 160 | event Approval(address indexed owner, address indexed spender, uint value); 161 | } 162 | 163 | library Address { 164 | function isContract(address account) internal view returns (bool) { 165 | bytes32 codehash; 166 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 167 | // solhint-disable-next-line no-inline-assembly 168 | assembly { codehash := extcodehash(account) } 169 | return (codehash != 0x0 && codehash != accountHash); 170 | } 171 | } 172 | 173 | library SafeERC20 { 174 | using SafeMath for uint256; 175 | using Address for address; 176 | 177 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 178 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 179 | } 180 | 181 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 182 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 183 | } 184 | 185 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 186 | require((value == 0) || (token.allowance(address(this), spender) == 0), 187 | "SafeERC20: approve from non-zero to non-zero allowance" 188 | ); 189 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 190 | } 191 | function callOptionalReturn(IERC20 token, bytes memory data) private { 192 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 193 | 194 | // solhint-disable-next-line avoid-low-level-calls 195 | (bool success, bytes memory returndata) = address(token).call(data); 196 | require(success, "SafeERC20: low-level call failed"); 197 | 198 | if (returndata.length > 0) { // Return data is optional 199 | // solhint-disable-next-line max-line-length 200 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 201 | } 202 | } 203 | } 204 | 205 | contract Oracle { 206 | function latestAnswer() external view returns (int256); 207 | } 208 | 209 | contract ImpermanentGain is ERC20Mintable { 210 | using SafeMath for *; 211 | using SafeERC20 for IERC20; 212 | 213 | bool public canBuy; 214 | 215 | address public treasury; 216 | address public baseToken; 217 | Oracle public oracle; 218 | 219 | uint256 public openTime; 220 | uint256 public closeTime; 221 | 222 | uint256 public openPrice; 223 | uint256 public closePrice; 224 | 225 | uint256 public constant leverage = 10; 226 | uint256 public constant protocolFee = 100; 227 | uint256 public constant minFee = 0.003e18; 228 | uint256 public constant maxFee = 0.03e18; 229 | 230 | 231 | // a + b = $1 232 | // b = tokenized put of impermanent loss 233 | // a = 1 - b 234 | uint256 public bPrice; 235 | 236 | uint256 public poolA; 237 | uint256 public poolB; 238 | 239 | mapping(address => uint256) public a; 240 | mapping(address => uint256) public b; 241 | 242 | event Mint(address indexed minter, uint256 amount); 243 | event Burn(address indexed burner, uint256 amount); 244 | event Swap(address indexed user, bool indexed a2b, uint256 input, uint256 output); 245 | event AddLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 246 | event RemoveLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 247 | 248 | function init(address _baseToken, address _oracle, address _treasury, uint256 _duration, uint256 _a, uint256 _b) public { 249 | require(openTime == 0, "Initialized"); 250 | require(_a > 0 && _b > 0, "No initial liquidity"); 251 | baseToken = _baseToken; 252 | oracle = Oracle(_oracle); 253 | treasury = _treasury; 254 | openTime = now; 255 | closeTime = now.add(_duration); 256 | openPrice = uint256(oracle.latestAnswer()); 257 | 258 | canBuy = true; 259 | 260 | name = "iGain LP token"; 261 | symbol = "iGLP"; 262 | decimals = ERC20Mintable(baseToken).decimals(); 263 | 264 | uint256 _lp = _a.mul(_b).sqrt(); 265 | poolA = _a; 266 | poolB = _b; 267 | _mint(msg.sender, _lp); 268 | _mint(address(0), 1000); //lock liquidity 269 | if(_b > _a) { 270 | a[msg.sender] = _b.sub(_a); 271 | doTransferIn(baseToken, msg.sender, _b); 272 | } 273 | else { 274 | b[msg.sender] = _a.sub(_b); 275 | doTransferIn(baseToken, msg.sender, _a); 276 | } 277 | emit AddLP(msg.sender, _a, _b, _lp); 278 | } 279 | 280 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountOut) { 281 | uint256 amountInWithFee = amountIn.mul(f); 282 | uint256 numerator = amountInWithFee.mul(reserveOut); 283 | uint256 denominator = reserveIn.mul(1e18).add(amountInWithFee); 284 | amountOut = numerator / denominator; 285 | } 286 | 287 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountIn) { 288 | uint numerator = reserveIn.mul(amountOut).mul(1e18); 289 | uint denominator = reserveOut.sub(amountOut).mul(f); 290 | amountIn = (numerator / denominator).add(1); 291 | } 292 | 293 | // calculate how many of a needs to be swapped for b when burning a 294 | function burnPartialHelper(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint256 f) internal pure returns (uint256 x) { 295 | uint256 r = amountIn.mul(4).mul(reserveIn).mul(f).div(1e18); //prevent stack too deep 296 | x = reserveOut.sub(amountIn).mul(f).div(1e18).add(reserveIn); // (reserveOut - a) * fee + reserveIn 297 | x = x.mul(x).add(r).sqrt().sub(x); 298 | x = x.mul(1e18).div(f).div(2); 299 | } 300 | 301 | // 1 - swap fee (numerator, in 1e18 format) 302 | function fee() public view returns (uint256) { 303 | uint256 time = now; 304 | uint256 _fee; 305 | if(time > closeTime) _fee = maxFee; 306 | else { 307 | _fee = minFee.add( 308 | time.sub(openTime).mul((maxFee.sub(minFee))).div(closeTime.sub(openTime)) 309 | ); 310 | } 311 | return (1e18).sub(_fee); 312 | } 313 | 314 | /***********************************| 315 | | mint/burn token | 316 | |__________________________________*/ 317 | 318 | // pay `amount` baseToken, get the same amount of a and b 319 | function mint(uint256 amount) external { 320 | require(canBuy, "cannot buy"); 321 | a[msg.sender] = a[msg.sender].add(amount); 322 | b[msg.sender] = b[msg.sender].add(amount); 323 | doTransferIn(baseToken, msg.sender, amount); 324 | } 325 | 326 | // burn `amount` of a and b, get `amount` baseToken 327 | function burn(uint256 amount) external { 328 | require(canBuy, "cannot buy"); 329 | a[msg.sender] = a[msg.sender].sub(amount); 330 | b[msg.sender] = b[msg.sender].sub(amount); 331 | doTransferOut(baseToken, msg.sender, amount); 332 | } 333 | 334 | // pay `amount` baseToken, get more than `min_a` of a 335 | function mintA(uint256 amount, uint256 min_a) external returns (uint256 _a) { 336 | require(canBuy, "cannot buy"); 337 | _a = getAmountOut(amount, poolB, poolA, fee()); 338 | poolB = poolB.add(amount); 339 | poolA = poolA.sub(_a); 340 | emit Swap(msg.sender, false, amount, _a); 341 | _a = _a.add(amount); 342 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 343 | a[msg.sender] = a[msg.sender].add(_a); 344 | doTransferIn(baseToken, msg.sender, amount); 345 | } 346 | 347 | // burn `_a` of a, receive more than `min_amount` of baseToken 348 | function burnA(uint256 _a, uint256 min_amount) external returns (uint256 amount) { 349 | require(canBuy, "cannot buy"); 350 | // amount = _a - x 351 | uint256 x = burnPartialHelper(_a, poolA, poolB, fee()); 352 | amount = _a.sub(x); 353 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 354 | 355 | // A = A + x 356 | // B = B - amount 357 | poolA = poolA.add(x); 358 | poolB = poolB.sub(amount); 359 | a[msg.sender] = a[msg.sender].sub(_a); 360 | emit Swap(msg.sender, true, x, amount); 361 | doTransferOut(baseToken, msg.sender, amount); 362 | } 363 | 364 | // pay `amount` baseToken, get more than `min_b` of b 365 | function mintB(uint256 amount, uint256 min_b) external returns (uint256 _b) { 366 | require(canBuy, "cannot buy"); 367 | _b = getAmountOut(amount, poolA, poolB, fee()); 368 | poolA = poolA.add(amount); 369 | poolB = poolB.sub(_b); 370 | emit Swap(msg.sender, true, amount, _b); 371 | _b = _b.add(amount); 372 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 373 | b[msg.sender] = b[msg.sender].add(_b); 374 | doTransferIn(baseToken, msg.sender, amount); 375 | } 376 | 377 | // burn `b` of b, receive more than `min_amount` of baseToken 378 | function burnB(uint256 _b, uint256 min_amount) external returns (uint256 amount) { 379 | require(canBuy, "cannot buy"); 380 | // amount = _b - x 381 | uint256 x = burnPartialHelper(_b, poolB, poolA, fee()); 382 | amount = _b.sub(x); 383 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 384 | 385 | // B = B + x 386 | // A = A - amount 387 | poolB = poolB.add(x); 388 | poolA = poolA.sub(amount); 389 | b[msg.sender] = b[msg.sender].sub(_b); 390 | emit Swap(msg.sender, false, x, amount); 391 | doTransferOut(baseToken, msg.sender, amount); 392 | } 393 | 394 | // pay `amount` baseToken, get more than `min_lp` liquidity provider share 395 | function mintLP(uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 396 | require(canBuy, "cannot buy"); 397 | // k = poolA * poolB 398 | // _lp = ( sqrt(_k)/sqrt(k) - 1 ) * LP 399 | uint256 k = poolA.mul(poolB).sqrt(); 400 | uint256 _k = poolA.add(amount).mul(poolB.add(amount)).sqrt(); 401 | _lp = _k.mul(1e18).div(k).sub(1e18).mul(_totalSupply).div(1e18); 402 | _lp = _lp.mul(fee()).div(1e18); //fee 403 | 404 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 405 | poolA = poolA.add(amount); 406 | poolB = poolB.add(amount); 407 | _mint(msg.sender, _lp); 408 | doTransferIn(baseToken, msg.sender, amount); 409 | emit AddLP(msg.sender, amount, amount, _lp); 410 | } 411 | 412 | // burn `lp` of liquidity provider share, recieve more than `min_amount` of baseToken 413 | function burnLP(uint256 lp, uint256 min_amount) external returns (uint256 amount) { 414 | require(canBuy, "cannot buy"); 415 | uint256 s = poolA.add(poolB); 416 | 417 | uint256 f = fee().mul(lp).div(_totalSupply); 418 | amount = poolA.mul(poolB).mul(4).mul(f).div(1e18); 419 | amount = amount.mul((2e18).sub(f)).div(1e18); 420 | amount = s.mul(s).sub(amount).sqrt(); 421 | amount = s.sub(amount).div(2); 422 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 423 | poolA = poolA.sub(amount); 424 | poolB = poolB.sub(amount); 425 | _burn(msg.sender, lp); 426 | doTransferOut(baseToken, msg.sender, amount); 427 | emit RemoveLP(msg.sender, amount, amount, lp); 428 | } 429 | 430 | 431 | /***********************************| 432 | | swap | 433 | |__________________________________*/ 434 | 435 | function swapAtoB(uint256 _a, uint256 min_b) external returns (uint256 _b) { 436 | require(canBuy, "cannot buy"); 437 | _b = getAmountOut(_a, poolA, poolB, fee()); 438 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 439 | poolA = poolA.add(_a); 440 | poolB = poolB.sub(_b); 441 | a[msg.sender] = a[msg.sender].sub(_a); 442 | b[msg.sender] = b[msg.sender].add(_b); 443 | emit Swap(msg.sender, true, _a, _b); 444 | } 445 | 446 | function swapBtoA(uint256 _b, uint256 min_a) external returns (uint256 _a) { 447 | require(canBuy, "cannot buy"); 448 | _a = getAmountOut(_b, poolB, poolA, fee()); 449 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 450 | poolB = poolB.add(_b); 451 | poolA = poolA.sub(_a); 452 | b[msg.sender] = b[msg.sender].sub(_b); 453 | a[msg.sender] = a[msg.sender].add(_a); 454 | emit Swap(msg.sender, false, _b, _a); 455 | } 456 | 457 | 458 | /***********************************| 459 | | add/remove liquidity | 460 | |__________________________________*/ 461 | 462 | // deposit `_a` of a and `_b` of b, get more than `min_lp` of liquidity provider share 463 | function depositLP(uint256 _a, uint256 _b, uint256 min_lp) external returns (uint256 _lp) { 464 | require(canBuy, "cannot buy"); 465 | // k = poolA * poolB 466 | // _lp = ( sqrt(_k)/sqrt(k) - 1 ) * LP 467 | uint256 k = poolA.mul(poolB).sqrt(); 468 | uint256 _k = poolA.add(_a).mul(poolB.add(_b)).sqrt(); 469 | _lp = _k.mul(1e18).div(k).sub(1e18).mul(_totalSupply).div(1e18); 470 | _lp = _lp.mul(fee()).div(1e18); //fee 471 | 472 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 473 | poolA = poolA.add(_a); 474 | poolB = poolB.add(_b); 475 | a[msg.sender] = a[msg.sender].sub(_a); 476 | b[msg.sender] = b[msg.sender].sub(_b); 477 | _mint(msg.sender, _lp); 478 | emit AddLP(msg.sender, _a, _b, _lp); 479 | } 480 | 481 | // burn no more than `max_lp` of liquidity provider share, withdraw `_a` of a and `_b` of b 482 | function withdrawLP(uint256 _a, uint256 _b, uint256 max_lp) external returns (uint256 _lp) { 483 | require(canBuy, "cannot buy"); 484 | // k = poolA * poolB 485 | // _lp = ( 1 - sqrt(_k)/sqrt(k) ) * LP 486 | uint256 k = poolA.mul(poolB).sqrt(); 487 | uint256 _k = poolA.sub(_a).mul(poolB.sub(_b)).sqrt(); 488 | _lp = (1e18).sub(_k.mul(1e18).div(k)).mul(_totalSupply).div(1e18); 489 | _lp = _lp.mul(1e18).div(fee()); //fee 490 | 491 | require(_lp <= max_lp, "SLIPPAGE_DETECTED"); 492 | poolA = poolA.sub(_a); 493 | poolB = poolB.sub(_b); 494 | a[msg.sender] = a[msg.sender].add(_a); 495 | b[msg.sender] = b[msg.sender].add(_b); 496 | _burn(msg.sender, _lp); 497 | emit RemoveLP(msg.sender, _a, _b, _lp); 498 | } 499 | 500 | 501 | /***********************************| 502 | | settlement | 503 | |__________________________________*/ 504 | 505 | // can only call once after closeTime 506 | // get price from oracle and calculate IL 507 | function close() external { 508 | require(now >= closeTime, "Not yet"); 509 | require(canBuy, "Closed"); 510 | canBuy = false; 511 | closePrice = uint256(oracle.latestAnswer()); 512 | uint256 ratio = openPrice.mul(1e18).div(closePrice); 513 | uint256 _bPrice = calcIL(ratio).mul(leverage); //leverage 514 | bPrice = _bPrice > 1e18 ? 1e18 : _bPrice; 515 | } 516 | 517 | function calcIL(uint256 ratio) public pure returns (uint256) { 518 | // 1 - sqrt(ratio) * 2 / (1 + ratio) 519 | return (1e18).sub(ratio.mul(1e18).sqrt().mul(2e18).div(ratio.add(1e18))); 520 | } 521 | 522 | // burn a, b, and lp and receive baseToken 523 | function claim() external returns (uint256 amount) { 524 | require(!canBuy, "Not yet"); 525 | 526 | uint256 _lp = _balances[msg.sender]; 527 | uint256 _a; 528 | uint256 _b; 529 | 530 | if(_lp > 0) { 531 | _a = poolA.mul(_lp).div(_totalSupply); 532 | _b = poolB.mul(_lp).div(_totalSupply); 533 | 534 | poolA = poolA.sub(_a); 535 | poolB = poolB.sub(_b); 536 | _burn(msg.sender, _lp); 537 | emit RemoveLP(msg.sender, _a, _b, _lp); 538 | } 539 | 540 | _a = _a.add(a[msg.sender]); 541 | _b = _b.add(b[msg.sender]); 542 | a[msg.sender] = 0; 543 | b[msg.sender] = 0; 544 | 545 | amount = _a.mul((1e18).sub(bPrice)).add(_b.mul(bPrice)).div(1e18); 546 | doTransferOut(baseToken, msg.sender, amount); 547 | } 548 | 549 | 550 | /***********************************| 551 | | helper function | 552 | |__________________________________*/ 553 | 554 | function doTransferIn(address tokenAddr, address from, uint amount) internal { 555 | IERC20 token = IERC20(tokenAddr); 556 | token.safeTransferFrom(from, address(this), amount); 557 | 558 | emit Mint(from, amount); 559 | } 560 | 561 | function doTransferOut(address tokenAddr, address to, uint amount) internal { 562 | uint256 _fee = amount.div(protocolFee); 563 | 564 | IERC20 token = IERC20(tokenAddr); 565 | token.safeTransfer(to, amount.sub(_fee)); 566 | token.safeTransfer(treasury, _fee); 567 | 568 | emit Burn(to, amount); 569 | } 570 | 571 | } 572 | -------------------------------------------------------------------------------- /v1/IGWithSig.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | library SafeMath { 4 | 5 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 6 | if (a == 0) 7 | return 0; 8 | uint256 c = a * b; 9 | require(c / a == b); 10 | return c; 11 | } 12 | 13 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 14 | require(b > 0); 15 | uint256 c = a / b; 16 | return c; 17 | } 18 | 19 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 20 | require(b <= a); 21 | uint256 c = a - b; 22 | return c; 23 | } 24 | 25 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 26 | uint256 c = a + b; 27 | require(c >= a); 28 | return c; 29 | } 30 | 31 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 32 | require(b != 0); 33 | return a % b; 34 | } 35 | 36 | function sqrt(uint256 x) internal pure returns (uint256) { 37 | uint256 z = add(x >> 1, 1); 38 | uint256 y = x; 39 | while (z < y) 40 | { 41 | y = z; 42 | z = ((add((x / z), z)) / 2); 43 | } 44 | return y; 45 | } 46 | } 47 | 48 | contract ERC20 { 49 | using SafeMath for uint256; 50 | 51 | mapping (address => uint256) internal _balances; 52 | mapping (address => mapping (address => uint256)) internal _allowed; 53 | 54 | event Transfer(address indexed from, address indexed to, uint256 value); 55 | event Approval(address indexed owner, address indexed spender, uint256 value); 56 | 57 | uint256 internal _totalSupply; 58 | 59 | /** 60 | * @dev Total number of tokens in existence 61 | */ 62 | function totalSupply() public view returns (uint256) { 63 | return _totalSupply; 64 | } 65 | 66 | /** 67 | * @dev Gets the balance of the specified address. 68 | * @param owner The address to query the balance of. 69 | * @return A uint256 representing the amount owned by the passed address. 70 | */ 71 | function balanceOf(address owner) public view returns (uint256) { 72 | return _balances[owner]; 73 | } 74 | 75 | /** 76 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 77 | * @param owner address The address which owns the funds. 78 | * @param spender address The address which will spend the funds. 79 | * @return A uint256 specifying the amount of tokens still available for the spender. 80 | */ 81 | function allowance(address owner, address spender) public view returns (uint256) { 82 | return _allowed[owner][spender]; 83 | } 84 | 85 | /** 86 | * @dev Transfer token to a specified address 87 | * @param to The address to transfer to. 88 | * @param value The amount to be transferred. 89 | */ 90 | function transfer(address to, uint256 value) public returns (bool) { 91 | _transfer(msg.sender, to, value); 92 | return true; 93 | } 94 | 95 | /** 96 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 97 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 98 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 99 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 100 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 101 | * @param spender The address which will spend the funds. 102 | * @param value The amount of tokens to be spent. 103 | */ 104 | function approve(address spender, uint256 value) public returns (bool) { 105 | _allowed[msg.sender][spender] = value; 106 | emit Approval(msg.sender, spender, value); 107 | return true; 108 | } 109 | 110 | /** 111 | * @dev Transfer tokens from one address to another. 112 | * Note that while this function emits an Approval event, this is not required as per the specification, 113 | * and other compliant implementations may not emit the event. 114 | * @param from address The address which you want to send tokens from 115 | * @param to address The address which you want to transfer to 116 | * @param value uint256 the amount of tokens to be transferred 117 | */ 118 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 119 | if (from != msg.sender && _allowed[from][msg.sender] != uint256(-1)) 120 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 121 | _transfer(from, to, value); 122 | return true; 123 | } 124 | 125 | function _transfer(address from, address to, uint256 value) internal { 126 | require(to != address(0)); 127 | _balances[from] = _balances[from].sub(value); 128 | _balances[to] = _balances[to].add(value); 129 | emit Transfer(from, to, value); 130 | } 131 | 132 | } 133 | 134 | contract ERC20Mintable is ERC20 { 135 | string public name; 136 | string public symbol; 137 | uint8 public decimals; 138 | 139 | function _mint(address to, uint256 amount) internal { 140 | _balances[to] = _balances[to].add(amount); 141 | _totalSupply = _totalSupply.add(amount); 142 | emit Transfer(address(0), to, amount); 143 | } 144 | 145 | function _burn(address from, uint256 amount) internal { 146 | _balances[from] = _balances[from].sub(amount); 147 | _totalSupply = _totalSupply.sub(amount); 148 | emit Transfer(from, address(0), amount); 149 | } 150 | } 151 | 152 | interface IERC20 { 153 | function totalSupply() external view returns (uint); 154 | function balanceOf(address account) external view returns (uint); 155 | function transfer(address recipient, uint amount) external returns (bool); 156 | function allowance(address owner, address spender) external view returns (uint); 157 | function approve(address spender, uint amount) external returns (bool); 158 | function transferFrom(address sender, address recipient, uint amount) external returns (bool); 159 | event Transfer(address indexed from, address indexed to, uint value); 160 | event Approval(address indexed owner, address indexed spender, uint value); 161 | } 162 | 163 | library Address { 164 | function isContract(address account) internal view returns (bool) { 165 | bytes32 codehash; 166 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 167 | // solhint-disable-next-line no-inline-assembly 168 | assembly { codehash := extcodehash(account) } 169 | return (codehash != 0x0 && codehash != accountHash); 170 | } 171 | } 172 | 173 | library SafeERC20 { 174 | using SafeMath for uint256; 175 | using Address for address; 176 | 177 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 178 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 179 | } 180 | 181 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 182 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 183 | } 184 | 185 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 186 | require((value == 0) || (token.allowance(address(this), spender) == 0), 187 | "SafeERC20: approve from non-zero to non-zero allowance" 188 | ); 189 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 190 | } 191 | function callOptionalReturn(IERC20 token, bytes memory data) private { 192 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 193 | 194 | // solhint-disable-next-line avoid-low-level-calls 195 | (bool success, bytes memory returndata) = address(token).call(data); 196 | require(success, "SafeERC20: low-level call failed"); 197 | 198 | if (returndata.length > 0) { // Return data is optional 199 | // solhint-disable-next-line max-line-length 200 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 201 | } 202 | } 203 | } 204 | 205 | contract Oracle { 206 | function latestAnswer() external view returns (int256); 207 | } 208 | 209 | contract ImpermanentGain is ERC20Mintable { 210 | using SafeMath for *; 211 | using SafeERC20 for IERC20; 212 | 213 | bool public canBuy; 214 | 215 | address public treasury; 216 | address public baseToken; 217 | Oracle public oracle; 218 | 219 | uint256 public openTime; 220 | uint256 public closeTime; 221 | 222 | uint256 public openPrice; 223 | uint256 public closePrice; 224 | 225 | uint256 public constant leverage = 10; 226 | uint256 public constant protocolFee = 100; 227 | uint256 public constant minFee = 0.003e18; 228 | uint256 public constant maxFee = 0.03e18; 229 | string public constant version = "1"; 230 | 231 | // --- EIP712 niceties --- 232 | bytes32 public DOMAIN_SEPARATOR; 233 | bytes32 public constant SIG_TYPEHASH = keccak256("MintWithSig(address maker,uint256 amount,uint256 price,bool isA,uint256 nonce,uint256 expiry)"); 234 | 235 | // a + b = $1 236 | // b = tokenized put of impermanent loss 237 | // a = 1 - b 238 | uint256 public bPrice; 239 | 240 | uint256 public poolA; 241 | uint256 public poolB; 242 | 243 | mapping(address => uint256) public a; 244 | mapping(address => uint256) public b; 245 | mapping(bytes32 => bool) public filledTx; 246 | 247 | event Mint(address indexed minter, uint256 amount); 248 | event Burn(address indexed burner, uint256 amount); 249 | event Swap(address indexed user, bool indexed a2b, uint256 input, uint256 output); 250 | event AddLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 251 | event RemoveLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 252 | 253 | function init(address _baseToken, address _oracle, address _treasury, uint256 _duration, uint256 _a, uint256 _b) public { 254 | require(openTime == 0, "Initialized"); 255 | require(_a > 0 && _b > 0, "No initial liquidity"); 256 | baseToken = _baseToken; 257 | oracle = Oracle(_oracle); 258 | treasury = _treasury; 259 | openTime = now; 260 | closeTime = now.add(_duration); 261 | openPrice = uint256(oracle.latestAnswer()); 262 | 263 | canBuy = true; 264 | 265 | name = "iGain LP token"; 266 | symbol = "iGLP"; 267 | decimals = ERC20Mintable(baseToken).decimals(); 268 | 269 | DOMAIN_SEPARATOR = keccak256(abi.encode( 270 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 271 | keccak256(bytes(name)), 272 | keccak256(bytes(version)), 273 | 1, //chainId 274 | address(this) 275 | )); 276 | 277 | uint256 _lp = _a.mul(_b).sqrt(); 278 | poolA = _a; 279 | poolB = _b; 280 | _mint(msg.sender, _lp); 281 | _mint(address(0), 1000); //lock liquidity 282 | if(_b > _a) { 283 | a[msg.sender] = _b.sub(_a); 284 | doTransferIn(baseToken, msg.sender, _b); 285 | } 286 | else { 287 | b[msg.sender] = _a.sub(_b); 288 | doTransferIn(baseToken, msg.sender, _a); 289 | } 290 | emit AddLP(msg.sender, _lp, _a, _b); 291 | } 292 | 293 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountOut) { 294 | uint256 amountInWithFee = amountIn.mul(f); 295 | uint256 numerator = amountInWithFee.mul(reserveOut); 296 | uint256 denominator = reserveIn.mul(1e18).add(amountInWithFee); 297 | amountOut = numerator / denominator; 298 | } 299 | 300 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountIn) { 301 | uint numerator = reserveIn.mul(amountOut).mul(1e18); 302 | uint denominator = reserveOut.sub(amountOut).mul(f); 303 | amountIn = (numerator / denominator).add(1); 304 | } 305 | 306 | // calculate how many of a needs to be swapped for b when burning a 307 | function burnPartialHelper(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint256 f) internal pure returns (uint256 x) { 308 | x = reserveOut.sub(amountIn).mul(f).div(1e18).add(reserveIn); // (reserveOut - a) * fee + reserveIn 309 | x = x.mul(x).add(amountIn.mul(4).mul(reserveIn).mul(f).div(1e18)).sqrt(); 310 | x = x.add(amountIn.mul(f).div(1e18)).sub(reserveOut.mul(f).div(1e18)).sub(reserveIn); 311 | x = x.mul(1e18).div(f).div(2); 312 | } 313 | 314 | // 1 - swap fee (numerator, in 1e18 format) 315 | function fee() public view returns (uint256) { 316 | uint256 time = now; 317 | uint256 _fee; 318 | if(time > closeTime) _fee = maxFee; 319 | else { 320 | _fee = closeTime.sub(time).mul(minFee).add( 321 | time.sub(openTime).mul(maxFee) 322 | ).div(closeTime.sub(openTime)); 323 | } 324 | return (1e18).sub(_fee); 325 | } 326 | 327 | /***********************************| 328 | | mint/burn token | 329 | |__________________________________*/ 330 | 331 | function mintWithSig(address maker, uint256 amount, uint256 price, bool isA, 332 | uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external 333 | { 334 | require(canBuy, "cannot buy"); 335 | 336 | bytes32 digest = 337 | keccak256(abi.encodePacked( 338 | "\x19\x01", 339 | DOMAIN_SEPARATOR, 340 | keccak256(abi.encode(SIG_TYPEHASH, 341 | maker, 342 | amount, 343 | price, 344 | isA, 345 | nonce, 346 | expiry)) 347 | )); 348 | 349 | require(maker == ecrecover(digest, v, r, s), "invalid-Sig"); 350 | require(expiry == 0 || now <= expiry, "order-expired"); 351 | require(!filledTx[digest], "order-filled"); 352 | 353 | filledTx[digest] = true; 354 | 355 | if(isA) { 356 | a[maker] = a[maker].add(amount); 357 | b[msg.sender] = b[msg.sender].add(amount); 358 | } 359 | else { 360 | a[msg.sender] = a[msg.sender].add(amount); 361 | b[maker] = b[maker].add(amount); 362 | } 363 | 364 | uint256 makerPayment = amount.mul(price).div(1e18); 365 | uint256 takerPayment = amount.sub(makerPayment); 366 | doTransferIn(baseToken, maker, makerPayment); 367 | doTransferIn(baseToken, msg.sender, takerPayment); 368 | } 369 | 370 | // pay `amount` baseToken, get the same amount of a and b 371 | function mint(uint256 amount) external { 372 | require(canBuy, "cannot buy"); 373 | a[msg.sender] = a[msg.sender].add(amount); 374 | b[msg.sender] = b[msg.sender].add(amount); 375 | doTransferIn(baseToken, msg.sender, amount); 376 | } 377 | 378 | // burn `amount` of a and b, get `amount` baseToken 379 | function burn(uint256 amount) external { 380 | require(canBuy, "cannot buy"); 381 | a[msg.sender] = a[msg.sender].sub(amount); 382 | b[msg.sender] = b[msg.sender].sub(amount); 383 | doTransferOut(baseToken, msg.sender, amount); 384 | } 385 | 386 | // pay `amount` baseToken, get more than `min_a` of a 387 | function mintA(uint256 amount, uint256 min_a) external returns (uint256 _a) { 388 | require(canBuy, "cannot buy"); 389 | _a = getAmountOut(amount, poolB, poolA, fee()); 390 | poolB = poolB.add(amount); 391 | poolA = poolA.sub(_a); 392 | emit Swap(msg.sender, false, amount, _a); 393 | _a = _a.add(amount); 394 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 395 | a[msg.sender] = a[msg.sender].add(_a); 396 | doTransferIn(baseToken, msg.sender, amount); 397 | } 398 | 399 | // burn `_a` of a, receive more than `min_amount` of baseToken 400 | function burnA(uint256 _a, uint256 min_amount) external returns (uint256 amount) { 401 | require(canBuy, "cannot buy"); 402 | // amount = _a - x 403 | uint256 x = burnPartialHelper(_a, poolA, poolB, fee()); 404 | amount = _a.sub(x); 405 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 406 | 407 | // A = A + x 408 | // B = B - amount 409 | poolA = poolA.add(x); 410 | poolB = poolB.sub(amount); 411 | a[msg.sender] = a[msg.sender].sub(_a); 412 | emit Swap(msg.sender, true, x, amount); 413 | doTransferOut(baseToken, msg.sender, amount); 414 | } 415 | 416 | // pay `amount` baseToken, get more than `min_b` of b 417 | function mintB(uint256 amount, uint256 min_b) external returns (uint256 _b) { 418 | require(canBuy, "cannot buy"); 419 | _b = getAmountOut(amount, poolA, poolB, fee()); 420 | poolA = poolA.add(amount); 421 | poolB = poolB.sub(_b); 422 | emit Swap(msg.sender, true, amount, _b); 423 | _b = _b.add(amount); 424 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 425 | b[msg.sender] = b[msg.sender].add(_b); 426 | doTransferIn(baseToken, msg.sender, amount); 427 | } 428 | 429 | // burn `b` of b, receive more than `min_amount` of baseToken 430 | function burnB(uint256 _b, uint256 min_amount) external returns (uint256 amount) { 431 | require(canBuy, "cannot buy"); 432 | // amount = _b - x 433 | uint256 x = burnPartialHelper(_b, poolB, poolA, fee()); 434 | amount = _b.sub(x); 435 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 436 | 437 | // B = B + x 438 | // A = A - amount 439 | poolB = poolB.add(x); 440 | poolA = poolA.sub(amount); 441 | b[msg.sender] = b[msg.sender].sub(_b); 442 | emit Swap(msg.sender, false, x, amount); 443 | doTransferOut(baseToken, msg.sender, amount); 444 | } 445 | 446 | // pay `amount` baseToken, get more than `min_lp` liquidity provider share 447 | function mintLP(uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 448 | require(canBuy, "cannot buy"); 449 | uint256 k = poolA.mul(poolB).sqrt(); 450 | uint256 _k = poolA.add(amount).mul(poolB.add(amount)).sqrt(); 451 | 452 | // ( sqrt(_k/k) - 1 ) * LP 453 | _lp = _k.mul(1e18).div(k).sub(1e18).mul(_totalSupply).div(1e18); 454 | _lp = _lp.mul(fee()).div(1e18); //fee 455 | 456 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 457 | poolA = poolA.add(amount); 458 | poolB = poolB.add(amount); 459 | _mint(msg.sender, _lp); 460 | doTransferIn(baseToken, msg.sender, amount); 461 | emit AddLP(msg.sender, _lp, amount, amount); 462 | } 463 | 464 | // burn `lp` of liquidity provider share, recieve more than `min_amount` of baseToken 465 | function burnLP(uint256 lp, uint256 min_amount) external returns (uint256 amount) { 466 | require(canBuy, "cannot buy"); 467 | uint256 s = poolA.add(poolB); 468 | amount = poolA.mul(poolB).mul(4).mul(fee()).mul(lp).div(1e18).div(_totalSupply); 469 | amount = amount.mul((2e18).sub(lp.mul(fee()).div(_totalSupply))).div(1e18); 470 | amount = s.mul(s).sub(amount).sqrt(); 471 | amount = s.sub(amount).div(2); 472 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 473 | poolA = poolA.sub(amount); 474 | poolB = poolB.sub(amount); 475 | _burn(msg.sender, lp); 476 | doTransferOut(baseToken, msg.sender, amount); 477 | emit RemoveLP(msg.sender, lp, amount, amount); 478 | } 479 | 480 | 481 | /***********************************| 482 | | swap | 483 | |__________________________________*/ 484 | 485 | function swapAtoB(uint256 _a, uint256 min_b) external returns (uint256 _b) { 486 | require(canBuy, "cannot buy"); 487 | _b = getAmountOut(_a, poolA, poolB, fee()); 488 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 489 | poolA = poolA.add(_a); 490 | poolB = poolB.sub(_b); 491 | a[msg.sender] = a[msg.sender].sub(_a); 492 | b[msg.sender] = b[msg.sender].add(_b); 493 | emit Swap(msg.sender, true, _a, _b); 494 | } 495 | 496 | function swapBtoA(uint256 _b, uint256 min_a) external returns (uint256 _a) { 497 | require(canBuy, "cannot buy"); 498 | _a = getAmountOut(_b, poolB, poolA, fee()); 499 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 500 | poolB = poolB.add(_b); 501 | poolA = poolA.sub(_a); 502 | b[msg.sender] = b[msg.sender].sub(_b); 503 | a[msg.sender] = a[msg.sender].add(_a); 504 | emit Swap(msg.sender, false, _b, _a); 505 | } 506 | 507 | 508 | /***********************************| 509 | | add/remove liquidity | 510 | |__________________________________*/ 511 | 512 | // deposit `_a` of a and `_b` of b, get more than `min_lp` of liquidity provider share 513 | function depositLP(uint256 _a, uint256 _b, uint256 min_lp) external returns (uint256 _lp) { 514 | require(canBuy, "cannot buy"); 515 | uint256 k = poolA.mul(poolB).sqrt(); 516 | uint256 _k = poolA.add(_a).mul(poolB.add(_b)).sqrt(); 517 | 518 | // ( sqrt(_k/k) - 1 ) * LP 519 | _lp = _k.mul(1e18).div(k).sub(1e18).mul(_totalSupply).div(1e18); 520 | _lp = _lp.mul(fee()).div(1e18); //fee 521 | 522 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 523 | poolA = poolA.add(_a); 524 | poolB = poolB.add(_b); 525 | a[msg.sender] = a[msg.sender].sub(_a); 526 | b[msg.sender] = b[msg.sender].sub(_b); 527 | _mint(msg.sender, _lp); 528 | emit AddLP(msg.sender, _lp, _a, _b); 529 | } 530 | 531 | // burn no more than `max_lp` of liquidity provider share, withdraw `_a` of a and `_b` of b 532 | function withdrawLP(uint256 _a, uint256 _b, uint256 max_lp) external returns (uint256 _lp) { 533 | require(canBuy, "cannot buy"); 534 | uint256 k = poolA.mul(poolB).sqrt(); 535 | uint256 _k = poolA.sub(_a).mul(poolB.sub(_b)).sqrt(); 536 | 537 | // ( 1 - sqrt(_k/k) ) * LP 538 | _lp = (1e18).sub(_k.mul(1e18).div(k)).mul(_totalSupply).div(1e18); 539 | _lp = _lp.mul(1e18).div(fee()); //fee 540 | 541 | require(_lp <= max_lp, "SLIPPAGE_DETECTED"); 542 | poolA = poolA.sub(_a); 543 | poolB = poolB.sub(_b); 544 | a[msg.sender] = a[msg.sender].add(_a); 545 | b[msg.sender] = b[msg.sender].add(_b); 546 | _burn(msg.sender, _lp); 547 | emit RemoveLP(msg.sender, _lp, _a, _b); 548 | } 549 | 550 | 551 | /***********************************| 552 | | settlement | 553 | |__________________________________*/ 554 | 555 | // can only call once after closeTime 556 | // get price from oracle and calculate IL 557 | function close() external { 558 | require(now >= closeTime, "Not yet"); 559 | require(canBuy, "Closed"); 560 | canBuy = false; 561 | closePrice = uint256(oracle.latestAnswer()); 562 | uint256 ratio = openPrice.mul(1e18).div(closePrice); 563 | uint256 _bPrice = calcIL(ratio).mul(leverage); //leverage 564 | bPrice = _bPrice > 1e18 ? 1e18 : _bPrice; 565 | } 566 | 567 | function calcIL(uint256 ratio) public pure returns (uint256) { 568 | // 1 - sqrt(ratio) * 2 / (1 + ratio) 569 | return (1e18).sub(ratio.mul(1e18).sqrt().mul(2e18).div(ratio.add(1e18))); 570 | } 571 | 572 | // burn a, b, and lp and receive baseToken 573 | function claim() external returns (uint256 amount) { 574 | require(!canBuy, "Not yet"); 575 | 576 | uint256 _lp = _balances[msg.sender]; 577 | uint256 _a; 578 | uint256 _b; 579 | 580 | if(_lp > 0) { 581 | _a = poolA.mul(_lp).div(_totalSupply); 582 | _b = poolB.mul(_lp).div(_totalSupply); 583 | 584 | poolA = poolA.sub(_a); 585 | poolB = poolB.sub(_b); 586 | _burn(msg.sender, _lp); 587 | emit RemoveLP(msg.sender, _lp, _a, _b); 588 | } 589 | 590 | _a = _a.add(a[msg.sender]); 591 | _b = _b.add(b[msg.sender]); 592 | a[msg.sender] = 0; 593 | b[msg.sender] = 0; 594 | 595 | amount = _a.mul((1e18).sub(bPrice)).add(_b.mul(bPrice)).div(1e18); 596 | doTransferOut(baseToken, msg.sender, amount); 597 | } 598 | 599 | 600 | /***********************************| 601 | | helper function | 602 | |__________________________________*/ 603 | 604 | function doTransferIn(address tokenAddr, address from, uint amount) internal { 605 | IERC20 token = IERC20(tokenAddr); 606 | token.safeTransferFrom(from, address(this), amount); 607 | 608 | emit Mint(from, amount); 609 | } 610 | 611 | function doTransferOut(address tokenAddr, address to, uint amount) internal { 612 | uint256 _fee = amount.div(protocolFee); 613 | 614 | IERC20 token = IERC20(tokenAddr); 615 | token.safeTransfer(to, amount.sub(_fee)); 616 | token.safeTransfer(treasury, _fee); 617 | 618 | emit Burn(to, amount); 619 | } 620 | 621 | } 622 | -------------------------------------------------------------------------------- /v2/IGainBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.7; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * @dev Provides information about the current execution context, including the 7 | * sender of the transaction and its data. While these are generally available 8 | * via msg.sender and msg.data, they should not be accessed in such a direct 9 | * manner, since when dealing with meta-transactions the account sending and 10 | * paying for execution may not be the actual sender (as far as an application 11 | * is concerned). 12 | * 13 | * This contract is only required for intermediate, library-like contracts. 14 | */ 15 | abstract contract Context { 16 | function _msgSender() internal view virtual returns (address) { 17 | return msg.sender; 18 | } 19 | 20 | function _msgData() internal view virtual returns (bytes calldata) { 21 | return msg.data; 22 | } 23 | } 24 | 25 | /** 26 | * @dev Interface of the ERC20 standard as defined in the EIP. 27 | */ 28 | interface IERC20 { 29 | /** 30 | * @dev Returns the amount of tokens in existence. 31 | */ 32 | function totalSupply() external view returns (uint256); 33 | 34 | /** 35 | * @dev Returns the amount of tokens owned by `account`. 36 | */ 37 | function balanceOf(address account) external view returns (uint256); 38 | 39 | /** 40 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 41 | * 42 | * Returns a boolean value indicating whether the operation succeeded. 43 | * 44 | * Emits a {Transfer} event. 45 | */ 46 | function transfer(address recipient, uint256 amount) external returns (bool); 47 | 48 | /** 49 | * @dev Returns the remaining number of tokens that `spender` will be 50 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 51 | * zero by default. 52 | * 53 | * This value changes when {approve} or {transferFrom} are called. 54 | */ 55 | function allowance(address owner, address spender) external view returns (uint256); 56 | 57 | /** 58 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 59 | * 60 | * Returns a boolean value indicating whether the operation succeeded. 61 | * 62 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 63 | * that someone may use both the old and the new allowance by unfortunate 64 | * transaction ordering. One possible solution to mitigate this race 65 | * condition is to first reduce the spender's allowance to 0 and set the 66 | * desired value afterwards: 67 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 68 | * 69 | * Emits an {Approval} event. 70 | */ 71 | function approve(address spender, uint256 amount) external returns (bool); 72 | 73 | /** 74 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 75 | * allowance mechanism. `amount` is then deducted from the caller's 76 | * allowance. 77 | * 78 | * Returns a boolean value indicating whether the operation succeeded. 79 | * 80 | * Emits a {Transfer} event. 81 | */ 82 | function transferFrom( 83 | address sender, 84 | address recipient, 85 | uint256 amount 86 | ) external returns (bool); 87 | 88 | /** 89 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 90 | * another (`to`). 91 | * 92 | * Note that `value` may be zero. 93 | */ 94 | event Transfer(address indexed from, address indexed to, uint256 value); 95 | 96 | /** 97 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 98 | * a call to {approve}. `value` is the new allowance. 99 | */ 100 | event Approval(address indexed owner, address indexed spender, uint256 value); 101 | } 102 | 103 | /** 104 | * @dev Collection of functions related to the address type 105 | */ 106 | library Address { 107 | /** 108 | * @dev Returns true if `account` is a contract. 109 | * 110 | * [IMPORTANT] 111 | * ==== 112 | * It is unsafe to assume that an address for which this function returns 113 | * false is an externally-owned account (EOA) and not a contract. 114 | * 115 | * Among others, `isContract` will return false for the following 116 | * types of addresses: 117 | * 118 | * - an externally-owned account 119 | * - a contract in construction 120 | * - an address where a contract will be created 121 | * - an address where a contract lived, but was destroyed 122 | * ==== 123 | */ 124 | function isContract(address account) internal view returns (bool) { 125 | // This method relies on extcodesize, which returns 0 for contracts in 126 | // construction, since the code is only stored at the end of the 127 | // constructor execution. 128 | 129 | uint256 size; 130 | assembly { 131 | size := extcodesize(account) 132 | } 133 | return size > 0; 134 | } 135 | 136 | /** 137 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 138 | * `recipient`, forwarding all available gas and reverting on errors. 139 | * 140 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 141 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 142 | * imposed by `transfer`, making them unable to receive funds via 143 | * `transfer`. {sendValue} removes this limitation. 144 | * 145 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 146 | * 147 | * IMPORTANT: because control is transferred to `recipient`, care must be 148 | * taken to not create reentrancy vulnerabilities. Consider using 149 | * {ReentrancyGuard} or the 150 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 151 | */ 152 | function sendValue(address payable recipient, uint256 amount) internal { 153 | require(address(this).balance >= amount, "Address: insufficient balance"); 154 | 155 | (bool success, ) = recipient.call{value: amount}(""); 156 | require(success, "Address: unable to send value, recipient may have reverted"); 157 | } 158 | 159 | /** 160 | * @dev Performs a Solidity function call using a low level `call`. A 161 | * plain `call` is an unsafe replacement for a function call: use this 162 | * function instead. 163 | * 164 | * If `target` reverts with a revert reason, it is bubbled up by this 165 | * function (like regular Solidity function calls). 166 | * 167 | * Returns the raw returned data. To convert to the expected return value, 168 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 169 | * 170 | * Requirements: 171 | * 172 | * - `target` must be a contract. 173 | * - calling `target` with `data` must not revert. 174 | * 175 | * _Available since v3.1._ 176 | */ 177 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 178 | return functionCall(target, data, "Address: low-level call failed"); 179 | } 180 | 181 | /** 182 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 183 | * `errorMessage` as a fallback revert reason when `target` reverts. 184 | * 185 | * _Available since v3.1._ 186 | */ 187 | function functionCall( 188 | address target, 189 | bytes memory data, 190 | string memory errorMessage 191 | ) internal returns (bytes memory) { 192 | return functionCallWithValue(target, data, 0, errorMessage); 193 | } 194 | 195 | /** 196 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 197 | * but also transferring `value` wei to `target`. 198 | * 199 | * Requirements: 200 | * 201 | * - the calling contract must have an ETH balance of at least `value`. 202 | * - the called Solidity function must be `payable`. 203 | * 204 | * _Available since v3.1._ 205 | */ 206 | function functionCallWithValue( 207 | address target, 208 | bytes memory data, 209 | uint256 value 210 | ) internal returns (bytes memory) { 211 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 212 | } 213 | 214 | /** 215 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 216 | * with `errorMessage` as a fallback revert reason when `target` reverts. 217 | * 218 | * _Available since v3.1._ 219 | */ 220 | function functionCallWithValue( 221 | address target, 222 | bytes memory data, 223 | uint256 value, 224 | string memory errorMessage 225 | ) internal returns (bytes memory) { 226 | require(address(this).balance >= value, "Address: insufficient balance for call"); 227 | require(isContract(target), "Address: call to non-contract"); 228 | 229 | (bool success, bytes memory returndata) = target.call{value: value}(data); 230 | return verifyCallResult(success, returndata, errorMessage); 231 | } 232 | 233 | /** 234 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 235 | * but performing a static call. 236 | * 237 | * _Available since v3.3._ 238 | */ 239 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 240 | return functionStaticCall(target, data, "Address: low-level static call failed"); 241 | } 242 | 243 | /** 244 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 245 | * but performing a static call. 246 | * 247 | * _Available since v3.3._ 248 | */ 249 | function functionStaticCall( 250 | address target, 251 | bytes memory data, 252 | string memory errorMessage 253 | ) internal view returns (bytes memory) { 254 | require(isContract(target), "Address: static call to non-contract"); 255 | 256 | (bool success, bytes memory returndata) = target.staticcall(data); 257 | return verifyCallResult(success, returndata, errorMessage); 258 | } 259 | 260 | /** 261 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 262 | * but performing a delegate call. 263 | * 264 | * _Available since v3.4._ 265 | */ 266 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 267 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 268 | } 269 | 270 | /** 271 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 272 | * but performing a delegate call. 273 | * 274 | * _Available since v3.4._ 275 | */ 276 | function functionDelegateCall( 277 | address target, 278 | bytes memory data, 279 | string memory errorMessage 280 | ) internal returns (bytes memory) { 281 | require(isContract(target), "Address: delegate call to non-contract"); 282 | 283 | (bool success, bytes memory returndata) = target.delegatecall(data); 284 | return verifyCallResult(success, returndata, errorMessage); 285 | } 286 | 287 | /** 288 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 289 | * revert reason using the provided one. 290 | * 291 | * _Available since v4.3._ 292 | */ 293 | function verifyCallResult( 294 | bool success, 295 | bytes memory returndata, 296 | string memory errorMessage 297 | ) internal pure returns (bytes memory) { 298 | if (success) { 299 | return returndata; 300 | } else { 301 | // Look for revert reason and bubble it up if present 302 | if (returndata.length > 0) { 303 | // The easiest way to bubble the revert reason is using memory via assembly 304 | 305 | assembly { 306 | let returndata_size := mload(returndata) 307 | revert(add(32, returndata), returndata_size) 308 | } 309 | } else { 310 | revert(errorMessage); 311 | } 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * @title SafeERC20 318 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 319 | * contract returns false). Tokens that return no value (and instead revert or 320 | * throw on failure) are also supported, non-reverting calls are assumed to be 321 | * successful. 322 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 323 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 324 | */ 325 | library SafeERC20 { 326 | using Address for address; 327 | 328 | function safeTransfer( 329 | IERC20 token, 330 | address to, 331 | uint256 value 332 | ) internal { 333 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 334 | } 335 | 336 | function safeTransferFrom( 337 | IERC20 token, 338 | address from, 339 | address to, 340 | uint256 value 341 | ) internal { 342 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 343 | } 344 | 345 | /** 346 | * @dev Deprecated. This function has issues similar to the ones found in 347 | * {IERC20-approve}, and its usage is discouraged. 348 | * 349 | * Whenever possible, use {safeIncreaseAllowance} and 350 | * {safeDecreaseAllowance} instead. 351 | */ 352 | function safeApprove( 353 | IERC20 token, 354 | address spender, 355 | uint256 value 356 | ) internal { 357 | // safeApprove should only be called when setting an initial allowance, 358 | // or when resetting it to zero. To increase and decrease it, use 359 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 360 | require( 361 | (value == 0) || (token.allowance(address(this), spender) == 0), 362 | "SafeERC20: approve from non-zero to non-zero allowance" 363 | ); 364 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 365 | } 366 | 367 | function safeIncreaseAllowance( 368 | IERC20 token, 369 | address spender, 370 | uint256 value 371 | ) internal { 372 | uint256 newAllowance = token.allowance(address(this), spender) + value; 373 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 374 | } 375 | 376 | function safeDecreaseAllowance( 377 | IERC20 token, 378 | address spender, 379 | uint256 value 380 | ) internal { 381 | unchecked { 382 | uint256 oldAllowance = token.allowance(address(this), spender); 383 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 384 | uint256 newAllowance = oldAllowance - value; 385 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 386 | } 387 | } 388 | 389 | /** 390 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 391 | * on the return value: the return value is optional (but if data is returned, it must not be false). 392 | * @param token The token targeted by the call. 393 | * @param data The call data (encoded using abi.encode or one of its variants). 394 | */ 395 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 396 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 397 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 398 | // the target address contains contract code and also asserts for success in the low-level call. 399 | 400 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 401 | if (returndata.length > 0) { 402 | // Return data is optional 403 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 404 | } 405 | } 406 | } 407 | 408 | /** 409 | * @dev Implementation of the {IERC20} interface. 410 | */ 411 | contract ERC20 is Context, IERC20 { 412 | mapping(address => uint256) internal _balances; 413 | 414 | mapping(address => mapping(address => uint256)) internal _allowances; 415 | 416 | uint256 internal _totalSupply; 417 | 418 | string public name; 419 | string public symbol; 420 | uint8 public decimals; 421 | 422 | /** 423 | * @dev See {IERC20-totalSupply}. 424 | */ 425 | function totalSupply() public view virtual override returns (uint256) { 426 | return _totalSupply; 427 | } 428 | 429 | /** 430 | * @dev See {IERC20-balanceOf}. 431 | */ 432 | function balanceOf(address account) public view virtual override returns (uint256) { 433 | return _balances[account]; 434 | } 435 | 436 | /** 437 | * @dev See {IERC20-transfer}. 438 | * 439 | * Requirements: 440 | * 441 | * - `recipient` cannot be the zero address. 442 | * - the caller must have a balance of at least `amount`. 443 | */ 444 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 445 | _transfer(_msgSender(), recipient, amount); 446 | return true; 447 | } 448 | 449 | /** 450 | * @dev See {IERC20-allowance}. 451 | */ 452 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 453 | return _allowances[owner][spender]; 454 | } 455 | 456 | /** 457 | * @dev See {IERC20-approve}. 458 | * 459 | * Requirements: 460 | * 461 | * - `spender` cannot be the zero address. 462 | */ 463 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 464 | _approve(_msgSender(), spender, amount); 465 | return true; 466 | } 467 | 468 | /** 469 | * @dev See {IERC20-transferFrom}. 470 | * 471 | * Emits an {Approval} event indicating the updated allowance. This is not 472 | * required by the EIP. See the note at the beginning of {ERC20}. 473 | * 474 | * Requirements: 475 | * 476 | * - `sender` and `recipient` cannot be the zero address. 477 | * - `sender` must have a balance of at least `amount`. 478 | * - the caller must have allowance for ``sender``'s tokens of at least 479 | * `amount`. 480 | */ 481 | function transferFrom( 482 | address sender, 483 | address recipient, 484 | uint256 amount 485 | ) public virtual override returns (bool) { 486 | _transfer(sender, recipient, amount); 487 | 488 | uint256 currentAllowance = _allowances[sender][_msgSender()]; 489 | require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); 490 | unchecked { 491 | _approve(sender, _msgSender(), currentAllowance - amount); 492 | } 493 | 494 | return true; 495 | } 496 | 497 | /** 498 | * @dev Moves `amount` of tokens from `sender` to `recipient`. 499 | * 500 | * This internal function is equivalent to {transfer}, and can be used to 501 | * e.g. implement automatic token fees, slashing mechanisms, etc. 502 | * 503 | * Emits a {Transfer} event. 504 | * 505 | * Requirements: 506 | * 507 | * - `sender` cannot be the zero address. 508 | * - `recipient` cannot be the zero address. 509 | * - `sender` must have a balance of at least `amount`. 510 | */ 511 | function _transfer( 512 | address sender, 513 | address recipient, 514 | uint256 amount 515 | ) internal virtual { 516 | 517 | uint256 senderBalance = _balances[sender]; 518 | require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); 519 | unchecked { 520 | _balances[sender] = senderBalance - amount; 521 | } 522 | _balances[recipient] += amount; 523 | 524 | emit Transfer(sender, recipient, amount); 525 | } 526 | 527 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 528 | * the total supply. 529 | * 530 | * Emits a {Transfer} event with `from` set to the zero address. 531 | * 532 | * Requirements: 533 | * 534 | * - `account` cannot be the zero address. 535 | */ 536 | function _mint(address account, uint256 amount) internal virtual { 537 | _totalSupply += amount; 538 | _balances[account] += amount; 539 | emit Transfer(address(0), account, amount); 540 | } 541 | 542 | /** 543 | * @dev Destroys `amount` tokens from `account`, reducing the 544 | * total supply. 545 | * 546 | * Emits a {Transfer} event with `to` set to the zero address. 547 | * 548 | * Requirements: 549 | * 550 | * - `account` cannot be the zero address. 551 | * - `account` must have at least `amount` tokens. 552 | */ 553 | function _burn(address account, uint256 amount) internal virtual { 554 | uint256 accountBalance = _balances[account]; 555 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 556 | unchecked { 557 | _balances[account] = accountBalance - amount; 558 | } 559 | _totalSupply -= amount; 560 | emit Transfer(account, address(0), amount); 561 | } 562 | 563 | /** 564 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 565 | * 566 | * This internal function is equivalent to `approve`, and can be used to 567 | * e.g. set automatic allowances for certain subsystems, etc. 568 | * 569 | * Emits an {Approval} event. 570 | * 571 | * Requirements: 572 | * 573 | * - `owner` cannot be the zero address. 574 | * - `spender` cannot be the zero address. 575 | */ 576 | function _approve( 577 | address owner, 578 | address spender, 579 | uint256 amount 580 | ) internal virtual { 581 | require(owner != address(0), "ERC20: approve from the zero address"); 582 | require(spender != address(0), "ERC20: approve to the zero address"); 583 | 584 | _allowances[owner][spender] = amount; 585 | emit Approval(owner, spender, amount); 586 | } 587 | } 588 | 589 | interface ERC20Mintable is IERC20 { 590 | function mint(address to, uint256 amount) external; 591 | function burn(address from, uint256 amount) external; 592 | } 593 | 594 | interface TokenFactory { 595 | function newToken(address _owner, string calldata _name, string calldata _symbol, uint8 _decimals) external returns (address token); 596 | } 597 | 598 | abstract contract Timestamp { 599 | function _blockTimestamp() internal view virtual returns (uint256) { 600 | return block.timestamp; 601 | } 602 | } 603 | 604 | abstract contract IGainBase is ERC20, Timestamp { 605 | using SafeERC20 for IERC20; 606 | 607 | bool public canBuy; 608 | 609 | address public treasury; 610 | address public baseToken; 611 | 612 | TokenFactory public constant Factory = TokenFactory(0x2eFC352936d5c52B3Ee061367c834BF768F11715); 613 | 614 | ERC20Mintable public a; 615 | ERC20Mintable public b; 616 | 617 | uint256 public openTime; 618 | uint256 public closeTime; 619 | 620 | uint256 public constant protocolFee = 0.01e18; 621 | uint256 public constant minFee = 0.003e18; 622 | uint256 public constant maxFee = 0.03e18; 623 | 624 | 625 | // a + b = $1 626 | // b = the synth 627 | // a = 1 - b 628 | uint256 public bPrice; 629 | 630 | uint256 public poolA; 631 | uint256 public poolB; 632 | 633 | event Mint(address indexed minter, uint256 amount); 634 | event Burn(address indexed burner, uint256 amount); 635 | event Swap(address indexed user, bool indexed a2b, uint256 input, uint256 output); 636 | event AddLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 637 | event RemoveLP(address indexed provider, uint256 a, uint256 b, uint256 lp); 638 | 639 | function sqrt(uint256 x) internal pure returns (uint256) { 640 | uint256 z = x >> 1 + 1; 641 | uint256 y = x; 642 | while (z < y) 643 | { 644 | y = z; 645 | z = (x / z + z) / 2; 646 | } 647 | return y; 648 | } 649 | 650 | // initializer, derived contracts must call this in its public init() function 651 | function _init(address _baseToken, address _treasury, string memory _batchName, uint256 _duration, uint256 _a, uint256 _b) internal { 652 | require(openTime == 0, "Initialized"); 653 | require(_a > 0 && _b > 0, "No initial liquidity"); 654 | baseToken = _baseToken; 655 | 656 | treasury = _treasury; 657 | openTime = _blockTimestamp(); 658 | closeTime = _blockTimestamp() + _duration; 659 | 660 | canBuy = true; 661 | 662 | name = string(abi.encodePacked("iGain LP token ", _batchName)); 663 | symbol = string(abi.encodePacked("iGLP ", _batchName)); 664 | decimals = ERC20(baseToken).decimals(); 665 | 666 | a = ERC20Mintable(Factory.newToken(address(this), string(abi.encodePacked("iGain A token ", _batchName)), string(abi.encodePacked("iG-A ", _batchName)), decimals)); 667 | b = ERC20Mintable(Factory.newToken(address(this), string(abi.encodePacked("iGain B token ", _batchName)), string(abi.encodePacked("iG-B ", _batchName)), decimals)); 668 | 669 | uint256 _lp = sqrt(_a * _b); 670 | poolA = _a; 671 | poolB = _b; 672 | _mint(_msgSender(), _lp); 673 | _mint(address(0), 1000); //lock liquidity 674 | if(_b > _a) { 675 | a.mint(_msgSender(), _b - _a); 676 | doTransferIn(baseToken, _msgSender(), _b); 677 | } 678 | else { 679 | b.mint(_msgSender(), _a - _b); 680 | doTransferIn(baseToken, _msgSender(), _a); 681 | } 682 | emit AddLP(_msgSender(), _a, _b, _lp); 683 | } 684 | 685 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountOut) { 686 | uint256 amountInWithFee = amountIn * f; 687 | uint256 numerator = amountInWithFee * reserveOut; 688 | uint256 denominator = reserveIn * 1e18 + amountInWithFee; 689 | amountOut = numerator / denominator; 690 | } 691 | 692 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut, uint256 f) internal pure returns (uint amountIn) { 693 | uint numerator = reserveIn * amountOut * 1e18; 694 | uint denominator = (reserveOut - amountOut) * f; 695 | amountIn = numerator / denominator + 1; 696 | } 697 | 698 | // calculate how many token needs to be swapped when minting/burning 699 | function swapPartialHelper(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint256 f) internal pure returns (uint256 x) { 700 | uint256 r = amountIn * 4 * reserveIn * f / 1e18; //prevent stack too deep 701 | x = (reserveOut - amountIn) * f / 1e18 + reserveIn; // (reserveOut - a) * fee + reserveIn 702 | x = sqrt(x * x + r) - x; 703 | x = x * 1e18 / f / 2; 704 | } 705 | 706 | // 1 - swap fee (numerator, in 1e18 format) 707 | function fee() public virtual view returns (uint256) { 708 | uint256 time = _blockTimestamp(); 709 | uint256 _fee; 710 | if(time < closeTime) { 711 | _fee = minFee + ( 712 | (time - openTime) * (maxFee - minFee) / (closeTime - openTime) 713 | ); 714 | } 715 | else { 716 | _fee = maxFee; 717 | } 718 | return 1e18 - _fee; 719 | } 720 | 721 | /***********************************| 722 | | mint/burn token | 723 | |__________________________________*/ 724 | 725 | // pay `amount` baseToken, get the same amount of a and b 726 | function mint(uint256 amount) external { 727 | require(canBuy, "cannot buy"); 728 | a.mint(_msgSender(), amount); 729 | b.mint(_msgSender(), amount); 730 | doTransferIn(baseToken, _msgSender(), amount); 731 | } 732 | 733 | // burn `amount` of a and b, get `amount` baseToken 734 | function burn(uint256 amount) external { 735 | require(canBuy, "cannot buy"); 736 | a.burn(_msgSender(), amount); 737 | b.burn(_msgSender(), amount); 738 | doTransferOut(baseToken, _msgSender(), amount); 739 | } 740 | 741 | // pay `amount` baseToken, get more than `min_a` of a 742 | function mintA(uint256 amount, uint256 min_a) external returns (uint256 _a) { 743 | require(canBuy, "cannot buy"); 744 | _a = getAmountOut(amount, poolB, poolA, fee()); 745 | poolB = poolB + amount; 746 | poolA = poolA - _a; 747 | emit Swap(_msgSender(), false, amount, _a); 748 | _a = _a + amount; 749 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 750 | a.mint(_msgSender(), _a); 751 | doTransferIn(baseToken, _msgSender(), amount); 752 | } 753 | 754 | // mint `_a` of a, pay no more than `max_amount` of baseToken 755 | function mintExactA(uint256 _a, uint256 max_amount) external returns (uint256 amount) { 756 | require(canBuy, "cannot buy"); 757 | amount = swapPartialHelper(_a, poolB, poolA, fee()); 758 | require(amount <= max_amount, "SLIPPAGE_DETECTED"); 759 | // y = _a - amount 760 | uint256 y = _a - amount; 761 | // A = A - y 762 | // B = B + amount 763 | poolA = poolA - y; 764 | poolB = poolB + amount; 765 | a.mint(_msgSender(), _a); 766 | emit Swap(_msgSender(), false, amount, y); 767 | doTransferIn(baseToken, _msgSender(), amount); 768 | } 769 | 770 | // burn `_a` of a, receive more than `min_amount` of baseToken 771 | function burnA(uint256 _a, uint256 min_amount) external returns (uint256 amount) { 772 | require(canBuy, "cannot buy"); 773 | // amount = _a - x 774 | uint256 x = swapPartialHelper(_a, poolA, poolB, fee()); 775 | amount = _a - x; 776 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 777 | 778 | // A = A + x 779 | // B = B - amount 780 | poolA = poolA + x; 781 | poolB = poolB - amount; 782 | a.burn(_msgSender(), _a); 783 | emit Swap(_msgSender(), true, x, amount); 784 | doTransferOut(baseToken, _msgSender(), amount); 785 | } 786 | 787 | // pay `amount` baseToken, get more than `min_b` of b 788 | function mintB(uint256 amount, uint256 min_b) external returns (uint256 _b) { 789 | require(canBuy, "cannot buy"); 790 | _b = getAmountOut(amount, poolA, poolB, fee()); 791 | poolA = poolA + amount; 792 | poolB = poolB - _b; 793 | emit Swap(_msgSender(), true, amount, _b); 794 | _b = _b + amount; 795 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 796 | b.mint(_msgSender(), _b); 797 | doTransferIn(baseToken, _msgSender(), amount); 798 | } 799 | 800 | // mint `_b` of b, pay no more than `max_amount` of baseToken 801 | function mintExactB(uint256 _b, uint256 max_amount) external returns (uint256 amount) { 802 | require(canBuy, "cannot buy"); 803 | amount = swapPartialHelper(_b, poolA, poolB, fee()); 804 | require(amount <= max_amount, "SLIPPAGE_DETECTED"); 805 | // y = _b - amount 806 | uint256 y = _b - amount; 807 | // B = B - y 808 | // A = A + amount 809 | poolB = poolB - y; 810 | poolA = poolA + amount; 811 | b.mint(_msgSender(), _b); 812 | emit Swap(_msgSender(), true, amount, y); 813 | doTransferIn(baseToken, _msgSender(), amount); 814 | } 815 | 816 | // burn `b` of b, receive more than `min_amount` of baseToken 817 | function burnB(uint256 _b, uint256 min_amount) external returns (uint256 amount) { 818 | require(canBuy, "cannot buy"); 819 | // amount = _b - x 820 | uint256 x = swapPartialHelper(_b, poolB, poolA, fee()); 821 | amount = _b - x; 822 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 823 | 824 | // B = B + x 825 | // A = A - amount 826 | poolB = poolB + x; 827 | poolA = poolA - amount; 828 | b.burn(_msgSender(), _b); 829 | emit Swap(_msgSender(), false, x, amount); 830 | doTransferOut(baseToken, _msgSender(), amount); 831 | } 832 | 833 | // pay `amount` baseToken, get more than `min_lp` liquidity provider share 834 | function mintLP(uint256 amount, uint256 min_lp) external returns (uint256 _lp) { 835 | require(canBuy, "cannot buy"); 836 | // k = poolA * poolB 837 | // _lp = ( sqrt(_k)/sqrt(k) - 1 ) * LP 838 | uint256 k = sqrt(poolA * poolB); 839 | uint256 _k = sqrt((poolA + amount) * (poolB + amount)); 840 | _lp = (_k * 1e18 / k - 1e18) * _totalSupply / 1e18; 841 | _lp = _lp * fee() / 1e18; //fee 842 | 843 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 844 | poolA = poolA + amount; 845 | poolB = poolB + amount; 846 | _mint(_msgSender(), _lp); 847 | doTransferIn(baseToken, _msgSender(), amount); 848 | emit AddLP(_msgSender(), amount, amount, _lp); 849 | } 850 | 851 | // burn `lp` of liquidity provider share, recieve more than `min_amount` of baseToken 852 | function burnLP(uint256 lp, uint256 min_amount) external returns (uint256 amount) { 853 | require(canBuy, "cannot buy"); 854 | uint256 s = poolA + poolB; 855 | 856 | uint256 f = fee() * lp / _totalSupply; 857 | amount = poolA * poolB * 4 * f / 1e18; 858 | amount = amount * (2e18 - f) / 1e18; 859 | amount = sqrt(s * s - amount); 860 | amount = (s - amount) / 2; 861 | require(amount >= min_amount, "SLIPPAGE_DETECTED"); 862 | poolA = poolA - amount; 863 | poolB = poolB - amount; 864 | _burn(_msgSender(), lp); 865 | doTransferOut(baseToken, _msgSender(), amount); 866 | emit RemoveLP(_msgSender(), amount, amount, lp); 867 | } 868 | 869 | /***********************************| 870 | | swap | 871 | |__________________________________*/ 872 | 873 | function swapAtoB(uint256 _a, uint256 min_b) external returns (uint256 _b) { 874 | require(canBuy, "cannot buy"); 875 | _b = getAmountOut(_a, poolA, poolB, fee()); 876 | require(_b >= min_b, "SLIPPAGE_DETECTED"); 877 | poolA = poolA + _a; 878 | poolB = poolB - _b; 879 | a.burn(_msgSender(), _a); 880 | b.mint(_msgSender(), _b); 881 | emit Swap(_msgSender(), true, _a, _b); 882 | } 883 | 884 | function swapBtoA(uint256 _b, uint256 min_a) external returns (uint256 _a) { 885 | require(canBuy, "cannot buy"); 886 | _a = getAmountOut(_b, poolB, poolA, fee()); 887 | require(_a >= min_a, "SLIPPAGE_DETECTED"); 888 | poolB = poolB + _b; 889 | poolA = poolA - _a; 890 | b.burn(_msgSender(), _b); 891 | a.mint(_msgSender(), _a); 892 | emit Swap(_msgSender(), false, _b, _a); 893 | } 894 | 895 | 896 | /***********************************| 897 | | add/remove liquidity | 898 | |__________________________________*/ 899 | 900 | // deposit `_a` of a and `_b` of b, get more than `min_lp` of liquidity provider share 901 | function depositLP(uint256 _a, uint256 _b, uint256 min_lp) external returns (uint256 _lp) { 902 | require(canBuy, "cannot buy"); 903 | // k = poolA * poolB 904 | // _lp = ( sqrt(_k)/sqrt(k) - 1 ) * LP 905 | uint256 k = sqrt(poolA * poolB); 906 | uint256 _k = sqrt((poolA + _a) * (poolB + _b)); 907 | _lp = (_k * 1e18 / k - 1e18) * _totalSupply / 1e18; 908 | _lp = _lp * fee() / 1e18; //fee 909 | 910 | require(_lp >= min_lp, "SLIPPAGE_DETECTED"); 911 | poolA = poolA + _a; 912 | poolB = poolB + _b; 913 | a.burn(_msgSender(), _a); 914 | b.burn(_msgSender(), _b); 915 | _mint(_msgSender(), _lp); 916 | emit AddLP(_msgSender(), _a, _b, _lp); 917 | } 918 | 919 | // burn no more than `max_lp` of liquidity provider share, withdraw `_a` of a and `_b` of b 920 | function withdrawLP(uint256 _a, uint256 _b, uint256 max_lp) external returns (uint256 _lp) { 921 | require(canBuy, "cannot buy"); 922 | // k = poolA * poolB 923 | // _lp = ( 1 - sqrt(_k)/sqrt(k) ) * LP 924 | uint256 k = sqrt(poolA * poolB); 925 | uint256 _k = sqrt((poolA - _a) * (poolB - _b)); 926 | _lp = (1e18 - _k * 1e18 / k) * _totalSupply / 1e18; 927 | _lp = _lp * 1e18 / fee(); //fee 928 | 929 | require(_lp <= max_lp, "SLIPPAGE_DETECTED"); 930 | poolA = poolA - _a; 931 | poolB = poolB - _b; 932 | a.mint(_msgSender(), _a); 933 | b.mint(_msgSender(), _b); 934 | _burn(_msgSender(), _lp); 935 | emit RemoveLP(_msgSender(), _a, _b, _lp); 936 | } 937 | 938 | 939 | /***********************************| 940 | | settlement | 941 | |__________________________________*/ 942 | 943 | // can only call once after closeTime 944 | // settle bPrice 945 | function close() external virtual; 946 | 947 | // burn a, b, and lp and receive baseToken 948 | function claim() external returns (uint256 amount) { 949 | require(!canBuy, "Not yet"); 950 | 951 | uint256 _lp = _balances[_msgSender()]; 952 | uint256 _a = a.balanceOf(_msgSender()); 953 | uint256 _b = b.balanceOf(_msgSender()); 954 | 955 | a.burn(_msgSender(), _a); 956 | b.burn(_msgSender(), _b); 957 | 958 | if(_lp > 0) { 959 | uint256 __a = poolA * _lp / _totalSupply; 960 | uint256 __b = poolB * _lp / _totalSupply; 961 | 962 | poolA = poolA - __a; 963 | poolB = poolB - __b; 964 | _a = _a + __a; 965 | _b = _b + __b; 966 | _burn(_msgSender(), _lp); 967 | emit RemoveLP(_msgSender(), _a, _b, _lp); 968 | } 969 | 970 | amount = (_a * (1e18 - bPrice) + _b * bPrice) / 1e18; 971 | doTransferOut(baseToken, _msgSender(), amount); 972 | } 973 | 974 | 975 | /***********************************| 976 | | helper function | 977 | |__________________________________*/ 978 | 979 | function doTransferIn(address tokenAddr, address from, uint amount) internal { 980 | IERC20 token = IERC20(tokenAddr); 981 | token.safeTransferFrom(from, address(this), amount); 982 | 983 | emit Mint(from, amount); 984 | } 985 | 986 | function doTransferOut(address tokenAddr, address to, uint amount) internal { 987 | uint256 _fee = amount * protocolFee / 1e18; 988 | 989 | IERC20 token = IERC20(tokenAddr); 990 | token.safeTransfer(to, amount - _fee); 991 | token.safeTransfer(treasury, _fee); 992 | 993 | emit Burn(to, amount); 994 | } 995 | 996 | } 997 | --------------------------------------------------------------------------------