diff --git a/package.json b/package.json
index d2dcec6b7..d4fdaa1e0 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
]
},
"scripts": {
+ "cf-build": "yarn && run-s build:dev-frontend",
"build": "run-s build:*",
"build:dev-frontend": "yarn workspace @liquity/dev-frontend build",
"build:subgraph": "yarn workspace @liquity/subgraph build",
@@ -53,10 +54,7 @@
"prepare:lib-base": "yarn workspace @liquity/lib-base prepare",
"prepare:lib-ethers": "yarn workspace @liquity/lib-ethers prepare",
"prepare:lib-react": "yarn workspace @liquity/lib-react prepare",
- "prepare:lib-subgraph": "yarn workspace @liquity/lib-subgraph prepare",
"prepare:providers": "yarn workspace @liquity/providers prepare",
- "prepare:subgraph": "yarn workspace @liquity/subgraph prepare",
- "prepare:docs": "run-s docs",
"rebuild": "run-s prepare build",
"release": "run-s release:*",
"release:delete-dev-deployments": "yarn workspace @liquity/lib-ethers delete-dev-deployments",
diff --git a/packages/contracts/contracts/B.Protocol/BAMM.sol b/packages/contracts/contracts/B.Protocol/BAMM.sol
new file mode 100644
index 000000000..ee6ee65b6
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/BAMM.sol
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./../StabilityPool.sol";
+import "./CropJoinAdapter.sol";
+import "./PriceFormula.sol";
+import "./../Interfaces/IPriceFeed.sol";
+import "./../Dependencies/IERC20.sol";
+import "./../Dependencies/SafeMath.sol";
+import "./../Dependencies/Ownable.sol";
+import "./../Dependencies/AggregatorV3Interface.sol";
+
+
+contract BAMM is CropJoinAdapter, PriceFormula, Ownable {
+ using SafeMath for uint256;
+
+ AggregatorV3Interface public immutable priceAggregator;
+ IERC20 public immutable LUSD;
+ StabilityPool immutable public SP;
+
+ address payable public immutable feePool;
+ uint public constant MAX_FEE = 100; // 1%
+ uint public fee = 0; // fee in bps
+ uint public A = 20;
+ uint public constant MIN_A = 20;
+ uint public constant MAX_A = 200;
+
+ uint public immutable maxDiscount; // max discount in bips
+
+ address public immutable frontEndTag;
+
+ uint constant public PRECISION = 1e18;
+
+ event ParamsSet(uint A, uint fee);
+ event UserDeposit(address indexed user, uint lusdAmount, uint numShares);
+ event UserWithdraw(address indexed user, uint lusdAmount, uint ethAmount, uint numShares);
+ event RebalanceSwap(address indexed user, uint lusdAmount, uint ethAmount, uint timestamp);
+
+ constructor(
+ address _priceAggregator,
+ address payable _SP,
+ address _LUSD,
+ address _LQTY,
+ uint _maxDiscount,
+ address payable _feePool,
+ address _fronEndTag)
+ public
+ CropJoinAdapter(_LQTY)
+ {
+ priceAggregator = AggregatorV3Interface(_priceAggregator);
+ LUSD = IERC20(_LUSD);
+ SP = StabilityPool(_SP);
+
+ feePool = _feePool;
+ maxDiscount = _maxDiscount;
+ frontEndTag = _fronEndTag;
+ }
+
+ function setParams(uint _A, uint _fee) external onlyOwner {
+ require(_fee <= MAX_FEE, "setParams: fee is too big");
+ require(_A >= MIN_A, "setParams: A too small");
+ require(_A <= MAX_A, "setParams: A too big");
+
+ fee = _fee;
+ A = _A;
+
+ emit ParamsSet(_A, _fee);
+ }
+
+ function fetchPrice() public view returns(uint) {
+ uint chainlinkDecimals;
+ uint chainlinkLatestAnswer;
+ uint chainlinkTimestamp;
+
+ // First, try to get current decimal precision:
+ try priceAggregator.decimals() returns (uint8 decimals) {
+ // If call to Chainlink succeeds, record the current decimal precision
+ chainlinkDecimals = decimals;
+ } catch {
+ // If call to Chainlink aggregator reverts, return a zero response with success = false
+ return 0;
+ }
+
+ // Secondly, try to get latest price data:
+ try priceAggregator.latestRoundData() returns
+ (
+ uint80 /* roundId */,
+ int256 answer,
+ uint256 /* startedAt */,
+ uint256 timestamp,
+ uint80 /* answeredInRound */
+ )
+ {
+ // If call to Chainlink succeeds, return the response and success = true
+ chainlinkLatestAnswer = uint(answer);
+ chainlinkTimestamp = timestamp;
+ } catch {
+ // If call to Chainlink aggregator reverts, return a zero response with success = false
+ return 0;
+ }
+
+ if(chainlinkTimestamp + 1 hours < now) return 0; // price is down
+
+ uint chainlinkFactor = 10 ** chainlinkDecimals;
+ return chainlinkLatestAnswer.mul(PRECISION) / chainlinkFactor;
+ }
+
+ function deposit(uint lusdAmount) external {
+ // update share
+ uint lusdValue = SP.getCompoundedLUSDDeposit(address(this));
+ uint ethValue = SP.getDepositorETHGain(address(this)).add(address(this).balance);
+
+ uint price = fetchPrice();
+ require(ethValue == 0 || price > 0, "deposit: chainlink is down");
+
+ uint totalValue = lusdValue.add(ethValue.mul(price) / PRECISION);
+
+ // this is in theory not reachable. if it is, better halt deposits
+ // the condition is equivalent to: (totalValue = 0) ==> (total = 0)
+ require(totalValue > 0 || total == 0, "deposit: system is rekt");
+
+ uint newShare = PRECISION;
+ if(total > 0) newShare = total.mul(lusdAmount) / totalValue;
+
+ // deposit
+ require(LUSD.transferFrom(msg.sender, address(this), lusdAmount), "deposit: transferFrom failed");
+ SP.provideToSP(lusdAmount, frontEndTag);
+
+ // update LP token
+ mint(msg.sender, newShare);
+
+ emit UserDeposit(msg.sender, lusdAmount, newShare);
+ }
+
+ function withdraw(uint numShares) external {
+ uint lusdValue = SP.getCompoundedLUSDDeposit(address(this));
+ uint ethValue = SP.getDepositorETHGain(address(this)).add(address(this).balance);
+
+ uint lusdAmount = lusdValue.mul(numShares).div(total);
+ uint ethAmount = ethValue.mul(numShares).div(total);
+
+ // this withdraws lusd, lqty, and eth
+ SP.withdrawFromSP(lusdAmount);
+
+ // update LP token
+ burn(msg.sender, numShares);
+
+ // send lusd and eth
+ if(lusdAmount > 0) LUSD.transfer(msg.sender, lusdAmount);
+ if(ethAmount > 0) {
+ (bool success, ) = msg.sender.call{ value: ethAmount }(""); // re-entry is fine here
+ require(success, "withdraw: sending ETH failed");
+ }
+
+ emit UserWithdraw(msg.sender, lusdAmount, ethAmount, numShares);
+ }
+
+ function addBps(uint n, int bps) internal pure returns(uint) {
+ require(bps <= 10000, "reduceBps: bps exceeds max");
+ require(bps >= -10000, "reduceBps: bps exceeds min");
+
+ return n.mul(uint(10000 + bps)) / 10000;
+ }
+
+ function getSwapEthAmount(uint lusdQty) public view returns(uint ethAmount, uint feeEthAmount) {
+ uint lusdBalance = SP.getCompoundedLUSDDeposit(address(this));
+ uint ethBalance = SP.getDepositorETHGain(address(this)).add(address(this).balance);
+
+ uint eth2usdPrice = fetchPrice();
+ if(eth2usdPrice == 0) return (0, 0); // chainlink is down
+
+ uint ethUsdValue = ethBalance.mul(eth2usdPrice) / PRECISION;
+ uint maxReturn = addBps(lusdQty.mul(PRECISION) / eth2usdPrice, int(maxDiscount));
+
+ uint xQty = lusdQty;
+ uint xBalance = lusdBalance;
+ uint yBalance = lusdBalance.add(ethUsdValue.mul(2));
+
+ uint usdReturn = getReturn(xQty, xBalance, yBalance, A);
+ uint basicEthReturn = usdReturn.mul(PRECISION) / eth2usdPrice;
+
+ if(ethBalance < basicEthReturn) basicEthReturn = ethBalance; // cannot give more than balance
+ if(maxReturn < basicEthReturn) basicEthReturn = maxReturn;
+
+ ethAmount = addBps(basicEthReturn, -int(fee));
+ feeEthAmount = basicEthReturn.sub(ethAmount);
+ }
+
+ // get ETH in return to LUSD
+ function swap(uint lusdAmount, uint minEthReturn, address payable dest) public returns(uint) {
+ (uint ethAmount, uint feeAmount) = getSwapEthAmount(lusdAmount);
+
+ require(ethAmount >= minEthReturn, "swap: low return");
+
+ LUSD.transferFrom(msg.sender, address(this), lusdAmount);
+ SP.provideToSP(lusdAmount, frontEndTag);
+
+ if(feeAmount > 0) feePool.transfer(feeAmount);
+ (bool success, ) = dest.call{ value: ethAmount }(""); // re-entry is fine here
+ require(success, "swap: sending ETH failed");
+
+ emit RebalanceSwap(msg.sender, lusdAmount, ethAmount, now);
+
+ return ethAmount;
+ }
+
+ // kyber network reserve compatible function
+ function trade(
+ IERC20 /* srcToken */,
+ uint256 srcAmount,
+ IERC20 /* destToken */,
+ address payable destAddress,
+ uint256 /* conversionRate */,
+ bool /* validate */
+ ) external payable returns (bool) {
+ return swap(srcAmount, 0, destAddress) > 0;
+ }
+
+ function getConversionRate(
+ IERC20 /* src */,
+ IERC20 /* dest */,
+ uint256 srcQty,
+ uint256 /* blockNumber */
+ ) external view returns (uint256) {
+ (uint ethQty, ) = getSwapEthAmount(srcQty);
+ return ethQty.mul(PRECISION) / srcQty;
+ }
+
+ receive() external payable {}
+}
diff --git a/packages/contracts/contracts/B.Protocol/BLens.sol b/packages/contracts/contracts/B.Protocol/BLens.sol
new file mode 100644
index 000000000..739835385
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/BLens.sol
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./BAMM.sol";
+import "./../Dependencies/SafeMath.sol";
+
+
+contract BLens {
+ function add(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require((z = x + y) >= x, "ds-math-add-overflow");
+ }
+ function sub(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require((z = x - y) <= x, "ds-math-sub-underflow");
+ }
+ function mul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
+ }
+ function divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(x, sub(y, 1)) / y;
+ }
+ uint256 constant WAD = 10 ** 18;
+ function wmul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, y) / WAD;
+ }
+ function wdiv(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, WAD) / y;
+ }
+ function wdivup(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = divup(mul(x, WAD), y);
+ }
+ uint256 constant RAY = 10 ** 27;
+ function rmul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, y) / RAY;
+ }
+ function rmulup(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = divup(mul(x, y), RAY);
+ }
+ function rdiv(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, RAY) / y;
+ }
+
+ function getUnclaimedLqty(address user, BAMM bamm, ERC20 token) external returns(uint) {
+ // trigger bamm (p)lqty claim
+ bamm.withdraw(0);
+
+ if(bamm.total() == 0) return 0;
+
+ // duplicate harvest logic
+ uint crop = sub(token.balanceOf(address(bamm)), bamm.stock());
+ uint share = add(bamm.share(), rdiv(crop, bamm.total()));
+
+ uint last = bamm.crops(user);
+ uint curr = rmul(bamm.stake(user), share);
+ if(curr > last) return curr - last;
+ return 0;
+ }
+}
diff --git a/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol b/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol
new file mode 100644
index 000000000..c956fe1f3
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./../TestContracts/PriceFeedTestnet.sol";
+
+/*
+* PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state
+* variable. The contract does not connect to a live Chainlink price feed.
+*/
+contract ChainlinkTestnet {
+
+ PriceFeedTestnet feed;
+ uint time = 0;
+
+ constructor(PriceFeedTestnet _feed) public {
+ feed = _feed;
+ }
+
+ function decimals() external pure returns(uint) {
+ return 18;
+ }
+
+ function setTimestamp(uint _time) external {
+ time = _time;
+ }
+
+ function latestRoundData() external view returns
+ (
+ uint80 /* roundId */,
+ int256 answer,
+ uint256 /* startedAt */,
+ uint256 timestamp,
+ uint80 /* answeredInRound */
+ )
+ {
+ answer = int(feed.getPrice());
+ if(time == 0 ) timestamp = now;
+ else timestamp = time;
+ }
+}
diff --git a/packages/contracts/contracts/B.Protocol/CropJoinAdapter.sol b/packages/contracts/contracts/B.Protocol/CropJoinAdapter.sol
new file mode 100644
index 000000000..f2f982f74
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/CropJoinAdapter.sol
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./crop.sol";
+import "./../StabilityPool.sol";
+
+// NOTE! - this is not an ERC20 token. transfer is not supported.
+contract CropJoinAdapter is CropJoin {
+ string constant public name = "B.AMM LUSD-ETH";
+ string constant public symbol = "LUSDETH";
+ uint constant public decimals = 18;
+
+ event Transfer(address indexed _from, address indexed _to, uint256 _value);
+
+ constructor(address _lqty) public
+ CropJoin(address(new Dummy()), "B.AMM", address(new DummyGem()), _lqty)
+ {
+ }
+
+ // adapter to cropjoin
+ function nav() public override returns (uint256) {
+ return total;
+ }
+
+ function totalSupply() public view returns (uint256) {
+ return total;
+ }
+
+ function balanceOf(address owner) public view returns (uint256 balance) {
+ balance = stake[owner];
+ }
+
+ function mint(address to, uint value) virtual internal {
+ join(to, value);
+ emit Transfer(address(0), to, value);
+ }
+
+ function burn(address owner, uint value) virtual internal {
+ exit(owner, value);
+ emit Transfer(owner, address(0), value);
+ }
+}
+
+contract Dummy {
+ fallback() external {}
+}
+
+contract DummyGem is Dummy {
+ function transfer(address, uint) external pure returns(bool) {
+ return true;
+ }
+
+ function transferFrom(address, address, uint) external pure returns(bool) {
+ return true;
+ }
+
+ function decimals() external pure returns(uint) {
+ return 18;
+ }
+}
\ No newline at end of file
diff --git a/packages/contracts/contracts/B.Protocol/MockePickle.sol b/packages/contracts/contracts/B.Protocol/MockePickle.sol
new file mode 100644
index 000000000..dc32e7210
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/MockePickle.sol
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+contract EIP20 {
+
+ event Transfer(address indexed _from, address indexed _to, uint256 _value);
+ event Approval(address indexed _owner, address indexed _spender, uint256 _value);
+
+ uint256 public totalSupply;
+ uint256 constant private MAX_UINT256 = 2**256 - 1;
+ mapping (address => uint256) public balances;
+ mapping (address => mapping (address => uint256)) public allowed;
+ /*
+ NOTE:
+ The following variables are OPTIONAL vanities. One does not have to include them.
+ They allow one to customise the token contract & in no way influences the core functionality.
+ Some wallets/interfaces might not even bother to look at this information.
+ */
+ string public name; //fancy name: eg Simon Bucks
+ uint8 public decimals; //How many decimals to show.
+ string public symbol; //An identifier: eg SBX
+
+ constructor (
+ uint256 _initialAmount,
+ string memory _tokenName,
+ uint8 _decimalUnits,
+ string memory _tokenSymbol
+ ) public {
+ balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
+ totalSupply = _initialAmount; // Update total supply
+ name = _tokenName; // Set the name for display purposes
+ decimals = _decimalUnits; // Amount of decimals for display purposes
+ symbol = _tokenSymbol; // Set the symbol for display purposes
+ }
+
+ function transfer(address _to, uint256 _value) public returns (bool success) {
+ require(balances[msg.sender] >= _value);
+ balances[msg.sender] -= _value;
+ balances[_to] += _value;
+ emit Transfer(msg.sender, _to, _value); //solhint-disable-line indent, no-unused-vars
+ return true;
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
+ uint256 allowance = allowed[_from][msg.sender];
+ require(balances[_from] >= _value && allowance >= _value);
+ balances[_to] += _value;
+ balances[_from] -= _value;
+ if (allowance < MAX_UINT256) {
+ allowed[_from][msg.sender] -= _value;
+ }
+ emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars
+ return true;
+ }
+
+ function balanceOf(address _owner) public view returns (uint256 balance) {
+ return balances[_owner];
+ }
+
+ function approve(address _spender, uint256 _value) public returns (bool success) {
+ allowed[msg.sender][_spender] = _value;
+ emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars
+ return true;
+ }
+
+ function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
+ return allowed[_owner][_spender];
+ }
+
+ // only for mock
+ function mint(address _to, uint _qty) public {
+ balances[_to] += _qty;
+ }
+}
+
+contract PickleJar {
+ EIP20 public token;
+ EIP20 public pToken;
+
+ constructor(EIP20 _token, EIP20 _ptoken) public {
+ token = _token;
+ pToken = _ptoken;
+ }
+
+ function depositAll() external {
+ uint userBalance = token.balanceOf(msg.sender);
+ require(token.transferFrom(msg.sender, address(this), userBalance), "depositAll: transferFrom failed");
+ pToken.mint(msg.sender, userBalance / 2); // 1 share = 2 token
+ }
+}
+
+
diff --git a/packages/contracts/contracts/B.Protocol/PBAMM.sol b/packages/contracts/contracts/B.Protocol/PBAMM.sol
new file mode 100644
index 000000000..50d2c371f
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/PBAMM.sol
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./BAMM.sol";
+
+interface PickleJarLike {
+ function depositAll() external;
+}
+
+contract PBAMM is BAMM {
+ PickleJarLike public immutable pickleJar;
+
+ constructor(
+ address _priceAggregator,
+ address payable _SP,
+ address _LUSD,
+ address _LQTY,
+ uint _maxDiscount,
+ address payable _feePool,
+ address _frontEndTag,
+ address _pLQTY,
+ address _pickleJar)
+ public
+ BAMM(_priceAggregator, _SP, _LUSD, _pLQTY, _maxDiscount, _feePool, _frontEndTag)
+ {
+ pickleJar = PickleJarLike(_pickleJar);
+
+ require(IERC20(_LQTY).approve(_pickleJar, type(uint).max), "constructor: approve failed");
+ }
+
+ // callable by anyone
+ function depositLqty() external {
+ SP.withdrawFromSP(0);
+ pickleJar.depositAll();
+ }
+
+ function mint(address to, uint value) override internal {
+ pickleJar.depositAll();
+ super.mint(to, value);
+ }
+
+ function burn(address owner, uint value) override internal {
+ pickleJar.depositAll();
+ super.burn(owner, value);
+ }
+}
diff --git a/packages/contracts/contracts/B.Protocol/PriceFormula.sol b/packages/contracts/contracts/B.Protocol/PriceFormula.sol
new file mode 100644
index 000000000..a0545a318
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/PriceFormula.sol
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.6.11;
+
+import "./../Dependencies/SafeMath.sol";
+
+contract PriceFormula {
+ using SafeMath for uint256;
+
+ function getSumFixedPoint(uint x, uint y, uint A) public pure returns(uint) {
+ if(x == 0 && y == 0) return 0;
+
+ uint sum = x.add(y);
+
+ for(uint i = 0 ; i < 255 ; i++) {
+ uint dP = sum;
+ dP = dP.mul(sum) / (x.mul(2)).add(1);
+ dP = dP.mul(sum) / (y.mul(2)).add(1);
+
+ uint prevSum = sum;
+
+ uint n = (A.mul(2).mul(x.add(y)).add(dP.mul(2))).mul(sum);
+ uint d = (A.mul(2).sub(1).mul(sum));
+ sum = n / d.add(dP.mul(3));
+
+ if(sum <= prevSum.add(1) && prevSum <= sum.add(1)) break;
+ }
+
+ return sum;
+ }
+
+ function getReturn(uint xQty, uint xBalance, uint yBalance, uint A) public pure returns(uint) {
+ uint sum = getSumFixedPoint(xBalance, yBalance, A);
+
+ uint c = sum.mul(sum) / (xQty.add(xBalance)).mul(2);
+ c = c.mul(sum) / A.mul(4);
+ uint b = (xQty.add(xBalance)).add(sum / A.mul(2));
+ uint yPrev = 0;
+ uint y = sum;
+
+ for(uint i = 0 ; i < 255 ; i++) {
+ yPrev = y;
+ uint n = (y.mul(y)).add(c);
+ uint d = y.mul(2).add(b).sub(sum);
+ y = n / d;
+
+ if(y <= yPrev.add(1) && yPrev <= y.add(1)) break;
+ }
+
+ return yBalance.sub(y).sub(1);
+ }
+}
diff --git a/packages/contracts/contracts/B.Protocol/crop.sol b/packages/contracts/contracts/B.Protocol/crop.sol
new file mode 100644
index 000000000..8eb279ef0
--- /dev/null
+++ b/packages/contracts/contracts/B.Protocol/crop.sol
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2021 Dai Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.6.11;
+
+interface VatLike {
+ function urns(bytes32, address) external view returns (uint256, uint256);
+ function gem(bytes32, address) external view returns (uint256);
+ function slip(bytes32, address, int256) external;
+}
+
+interface ERC20 {
+ function balanceOf(address owner) external view returns (uint256);
+ function transfer(address dst, uint256 amount) external returns (bool);
+ function transferFrom(address src, address dst, uint256 amount) external returns (bool);
+ function approve(address spender, uint256 amount) external returns (bool);
+ function allowance(address owner, address spender) external view returns (uint256);
+ function decimals() external returns (uint8);
+}
+
+// receives tokens and shares them among holders
+contract CropJoin {
+
+ VatLike public immutable vat; // cdp engine
+ bytes32 public immutable ilk; // collateral type
+ ERC20 public immutable gem; // collateral token
+ uint256 public immutable dec; // gem decimals
+ ERC20 public immutable bonus; // rewards token
+
+ uint256 public share; // crops per gem [ray]
+ uint256 public total; // total gems [wad]
+ uint256 public stock; // crop balance [wad]
+
+ mapping (address => uint256) public crops; // crops per user [wad]
+ mapping (address => uint256) public stake; // gems per user [wad]
+
+ uint256 immutable internal to18ConversionFactor;
+ uint256 immutable internal toGemConversionFactor;
+
+ // --- Events ---
+ event Join(uint256 val);
+ event Exit(uint256 val);
+ event Flee();
+ event Tack(address indexed src, address indexed dst, uint256 wad);
+
+ constructor(address vat_, bytes32 ilk_, address gem_, address bonus_) public {
+ vat = VatLike(vat_);
+ ilk = ilk_;
+ gem = ERC20(gem_);
+ uint256 dec_ = ERC20(gem_).decimals();
+ require(dec_ <= 18);
+ dec = dec_;
+ to18ConversionFactor = 10 ** (18 - dec_);
+ toGemConversionFactor = 10 ** dec_;
+
+ bonus = ERC20(bonus_);
+ }
+
+ function add(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require((z = x + y) >= x, "ds-math-add-overflow");
+ }
+ function sub(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require((z = x - y) <= x, "ds-math-sub-underflow");
+ }
+ function mul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
+ }
+ function divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(x, sub(y, 1)) / y;
+ }
+ uint256 constant WAD = 10 ** 18;
+ function wmul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, y) / WAD;
+ }
+ function wdiv(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, WAD) / y;
+ }
+ function wdivup(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = divup(mul(x, WAD), y);
+ }
+ uint256 constant RAY = 10 ** 27;
+ function rmul(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, y) / RAY;
+ }
+ function rmulup(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = divup(mul(x, y), RAY);
+ }
+ function rdiv(uint256 x, uint256 y) public pure returns (uint256 z) {
+ z = mul(x, RAY) / y;
+ }
+
+ // Net Asset Valuation [wad]
+ function nav() public virtual returns (uint256) {
+ uint256 _nav = gem.balanceOf(address(this));
+ return mul(_nav, to18ConversionFactor);
+ }
+
+ // Net Assets per Share [wad]
+ function nps() public returns (uint256) {
+ if (total == 0) return WAD;
+ else return wdiv(nav(), total);
+ }
+
+ function crop() internal virtual returns (uint256) {
+ return sub(bonus.balanceOf(address(this)), stock);
+ }
+
+ function harvest(address from, address to) internal {
+ if (total > 0) share = add(share, rdiv(crop(), total));
+
+ uint256 last = crops[from];
+ uint256 curr = rmul(stake[from], share);
+ if (curr > last) require(bonus.transfer(to, curr - last));
+ stock = bonus.balanceOf(address(this));
+ }
+
+ function join(address urn, uint256 val) internal virtual {
+ harvest(urn, urn);
+ if (val > 0) {
+ uint256 wad = wdiv(mul(val, to18ConversionFactor), nps());
+
+ // Overflow check for int256(wad) cast below
+ // Also enforces a non-zero wad
+ require(int256(wad) > 0);
+
+ require(gem.transferFrom(msg.sender, address(this), val));
+ vat.slip(ilk, urn, int256(wad));
+
+ total = add(total, wad);
+ stake[urn] = add(stake[urn], wad);
+ }
+ crops[urn] = rmulup(stake[urn], share);
+ emit Join(val);
+ }
+
+ function exit(address guy, uint256 val) internal virtual {
+ harvest(msg.sender, guy);
+ if (val > 0) {
+ uint256 wad = wdivup(mul(val, to18ConversionFactor), nps());
+
+ // Overflow check for int256(wad) cast below
+ // Also enforces a non-zero wad
+ require(int256(wad) > 0);
+
+ require(gem.transfer(guy, val));
+ vat.slip(ilk, msg.sender, -int256(wad));
+
+ total = sub(total, wad);
+ stake[msg.sender] = sub(stake[msg.sender], wad);
+ }
+ crops[msg.sender] = rmulup(stake[msg.sender], share);
+ emit Exit(val);
+ }
+}
diff --git a/packages/contracts/contracts/Dependencies/Ownable.sol b/packages/contracts/contracts/Dependencies/Ownable.sol
index d1f4826e6..39fcb3fce 100644
--- a/packages/contracts/contracts/Dependencies/Ownable.sol
+++ b/packages/contracts/contracts/Dependencies/Ownable.sol
@@ -49,6 +49,17 @@ contract Ownable {
return msg.sender == _owner;
}
+ function transferOwnership(address newOwner) public virtual onlyOwner {
+ require(newOwner != address(0), "Ownable: new owner is the zero address");
+ _setOwner(newOwner);
+ }
+
+ function _setOwner(address newOwner) private {
+ address oldOwner = _owner;
+ _owner = newOwner;
+ emit OwnershipTransferred(oldOwner, newOwner);
+ }
+
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore.
diff --git a/packages/contracts/test/B.Protocol/BAMMTest.js b/packages/contracts/test/B.Protocol/BAMMTest.js
new file mode 100644
index 000000000..3674ca5e9
--- /dev/null
+++ b/packages/contracts/test/B.Protocol/BAMMTest.js
@@ -0,0 +1,801 @@
+const deploymentHelper = require("./../../utils/deploymentHelpers.js")
+const testHelpers = require("./../../utils/testHelpers.js")
+const th = testHelpers.TestHelper
+const dec = th.dec
+const toBN = th.toBN
+const mv = testHelpers.MoneyValues
+const timeValues = testHelpers.TimeValues
+
+const TroveManagerTester = artifacts.require("TroveManagerTester")
+const LUSDToken = artifacts.require("LUSDToken")
+const NonPayable = artifacts.require('NonPayable.sol')
+const BAMM = artifacts.require("BAMM.sol")
+const BLens = artifacts.require("BLens.sol")
+const ChainlinkTestnet = artifacts.require("ChainlinkTestnet.sol")
+
+const ZERO = toBN('0')
+const ZERO_ADDRESS = th.ZERO_ADDRESS
+const maxBytes32 = th.maxBytes32
+
+const getFrontEndTag = async (stabilityPool, depositor) => {
+ return (await stabilityPool.deposits(depositor))[1]
+}
+
+contract('BAMM', async accounts => {
+ const [owner,
+ defaulter_1, defaulter_2, defaulter_3,
+ whale,
+ alice, bob, carol, dennis, erin, flyn,
+ A, B, C, D, E, F,
+ u1, u2, u3, u4, u5,
+ v1, v2, v3, v4, v5,
+ frontEnd_1, frontEnd_2, frontEnd_3,
+ bammOwner
+ ] = accounts;
+
+ const [bountyAddress, lpRewardsAddress, multisig] = accounts.slice(997, 1000)
+
+ const frontEnds = [frontEnd_1, frontEnd_2, frontEnd_3]
+ let contracts
+ let priceFeed
+ let lusdToken
+ let sortedTroves
+ let troveManager
+ let activePool
+ let stabilityPool
+ let bamm
+ let lens
+ let chainlink
+ let defaultPool
+ let borrowerOperations
+ let lqtyToken
+ let communityIssuance
+
+ let gasPriceInWei
+
+ const feePool = "0x1000000000000000000000000000000000000001"
+
+ const getOpenTroveLUSDAmount = async (totalDebt) => th.getOpenTroveLUSDAmount(contracts, totalDebt)
+ const openTrove = async (params) => th.openTrove(contracts, params)
+ //const assertRevert = th.assertRevert
+
+ describe("BAMM", async () => {
+
+ before(async () => {
+ gasPriceInWei = await web3.eth.getGasPrice()
+ })
+
+ beforeEach(async () => {
+ contracts = await deploymentHelper.deployLiquityCore()
+ contracts.troveManager = await TroveManagerTester.new()
+ contracts.lusdToken = await LUSDToken.new(
+ contracts.troveManager.address,
+ contracts.stabilityPool.address,
+ contracts.borrowerOperations.address
+ )
+ const LQTYContracts = await deploymentHelper.deployLQTYContracts(bountyAddress, lpRewardsAddress, multisig)
+
+ priceFeed = contracts.priceFeedTestnet
+ lusdToken = contracts.lusdToken
+ sortedTroves = contracts.sortedTroves
+ troveManager = contracts.troveManager
+ activePool = contracts.activePool
+ stabilityPool = contracts.stabilityPool
+ defaultPool = contracts.defaultPool
+ borrowerOperations = contracts.borrowerOperations
+ hintHelpers = contracts.hintHelpers
+
+ lqtyToken = LQTYContracts.lqtyToken
+ communityIssuance = LQTYContracts.communityIssuance
+
+ await deploymentHelper.connectLQTYContracts(LQTYContracts)
+ await deploymentHelper.connectCoreContracts(contracts, LQTYContracts)
+ await deploymentHelper.connectLQTYContractsToCore(LQTYContracts, contracts)
+
+ // Register 3 front ends
+ //await th.registerFrontEnds(frontEnds, stabilityPool)
+
+ // deploy BAMM
+ chainlink = await ChainlinkTestnet.new(priceFeed.address)
+
+ const kickbackRate_F1 = toBN(dec(5, 17)) // F1 kicks 50% back to depositor
+ await stabilityPool.registerFrontEnd(kickbackRate_F1, { from: frontEnd_1 })
+
+ bamm = await BAMM.new(chainlink.address, stabilityPool.address, lusdToken.address, lqtyToken.address, 400, feePool, frontEnd_1, {from: bammOwner})
+ lens = await BLens.new()
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): increases the Stability Pool LUSD balance", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await bamm.deposit(toBN(200), { from: alice })
+
+ // check LUSD balances after
+ const stabilityPool_LUSD_After = await stabilityPool.getTotalLUSDDeposits()
+ assert.equal(stabilityPool_LUSD_After, 200)
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): two users deposit, check their share", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: whale } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await lusdToken.approve(bamm.address, toBN(200), { from: whale })
+ await bamm.deposit(toBN(200), { from: alice })
+ await bamm.deposit(toBN(200), { from: whale })
+
+ // check LUSD balances after1
+ const whaleShare = await bamm.stake(whale)
+ const aliceShare = await bamm.stake(alice)
+
+ assert.equal(whaleShare.toString(), aliceShare.toString())
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): two users deposit, one withdraw. check their share", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: whale } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await lusdToken.approve(bamm.address, toBN(100), { from: whale })
+ await bamm.deposit(toBN(200), { from: alice })
+ await bamm.deposit(toBN(100), { from: whale })
+
+ // check LUSD balances after1
+ const whaleShare = await bamm.stake(whale)
+ const aliceShare = await bamm.stake(alice)
+
+ assert.equal(whaleShare.mul(toBN(2)).toString(), aliceShare.toString())
+
+ const whaleBalanceBefore = await lusdToken.balanceOf(whale)
+ const shareToWithdraw = whaleShare.div(toBN(2));
+ await bamm.withdraw(shareToWithdraw, { from: whale });
+
+ const newWhaleShare = await bamm.stake(whale)
+ assert.equal(newWhaleShare.mul(toBN(2)).toString(), whaleShare.toString())
+
+ const whaleBalanceAfter = await lusdToken.balanceOf(whale)
+ assert.equal(whaleBalanceAfter.sub(whaleBalanceBefore).toString(), 50)
+ })
+
+ it('rebalance scenario', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ bamm.deposit(whaleLUSD, { from: whale } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+ // Alice makes Trove and withdraws 100 LUSD
+ await openTrove({ extraLUSDAmount: toBN(dec(100, 18)), ICR: toBN(dec(5, 18)), extraParams: { from: alice, value: dec(50, 'ether') } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+ console.log("rebalance", (await bamm.fetchPrice()).toString())
+
+ const SPLUSD_Before = await stabilityPool.getTotalLUSDDeposits()
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // Confirm SP has decreased
+ const SPLUSD_After = await stabilityPool.getTotalLUSDDeposits()
+ assert.isTrue(SPLUSD_After.lt(SPLUSD_Before))
+
+ console.log((await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ console.log((await stabilityPool.getDepositorETHGain(bamm.address)).toString())
+ const price = await priceFeed.fetchPrice.call()
+ console.log(price.toString())
+
+ const ammExpectedEth = await bamm.getSwapEthAmount.call(toBN(dec(1, 18)))
+
+ console.log("expected eth amount", ammExpectedEth.ethAmount.toString())
+
+ const rate = await bamm.getConversionRate(lusdToken.address, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", toBN(dec(1, 18)), 0)
+ assert.equal(rate.toString(), ammExpectedEth.ethAmount.toString())
+
+ await lusdToken.approve(bamm.address, toBN(dec(1, 18)), { from: alice })
+
+ const dest = "0xe1A587Ac322da1611DF55b11A6bC8c6052D896cE" // dummy address
+ //await bamm.swap(toBN(dec(1, 18)), dest, { from: alice })
+ await bamm.trade(lusdToken.address, toBN(dec(1, 18)), "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", dest, rate, true, { from: alice });
+
+ const swapBalance = await web3.eth.getBalance(dest)
+
+ assert.equal(swapBalance, ammExpectedEth.ethAmount)
+ })
+
+ it("test basic LQTY allocation", async () => {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+
+ // A, B, C, open troves
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: B } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: D } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: E } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: F } })
+
+ // D, E provide to bamm, F provide to SP
+ await lusdToken.approve(bamm.address, dec(1000, 18), { from: D })
+ await lusdToken.approve(bamm.address, dec(2000, 18), { from: E })
+ await bamm.deposit(dec(1000, 18), { from: D })
+ await bamm.deposit(dec(2000, 18), { from: E })
+ await stabilityPool.provideToSP(dec(3000, 18), frontEnd_1, { from: F })
+
+ // Get F1, F2, F3 LQTY balances before, and confirm they're zero
+ const D_LQTYBalance_Before = await lqtyToken.balanceOf(D)
+ const E_LQTYBalance_Before = await lqtyToken.balanceOf(E)
+ const F_LQTYBalance_Before = await lqtyToken.balanceOf(F)
+
+ assert.equal(D_LQTYBalance_Before, '0')
+ assert.equal(E_LQTYBalance_Before, '0')
+ assert.equal(F_LQTYBalance_Before, '0')
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ const expectdDLqtyDelta = await lens.getUnclaimedLqty.call(D, bamm.address, lqtyToken.address)
+ const expectdELqtyDelta = await lens.getUnclaimedLqty.call(E, bamm.address, lqtyToken.address)
+
+ await stabilityPool.withdrawFromSP(0, { from: F })
+ await bamm.withdraw(0, { from: D })
+ await bamm.withdraw(0, { from: E })
+
+ // Get F1, F2, F3 LQTY balances after, and confirm they have increased
+ const D_LQTYBalance_After = await lqtyToken.balanceOf(D)
+ const E_LQTYBalance_After = await lqtyToken.balanceOf(E)
+ const F_LQTYBalance_After = await lqtyToken.balanceOf(F)
+
+ assert((await lqtyToken.balanceOf(frontEnd_1)).gt(toBN(0)))
+ assert.equal(D_LQTYBalance_After.sub(D_LQTYBalance_Before).toString(), expectdDLqtyDelta.toString())
+ assert.equal(E_LQTYBalance_After.sub(E_LQTYBalance_Before).toString(), expectdELqtyDelta.toString())
+
+ assert.equal(D_LQTYBalance_After.add(E_LQTYBalance_After).toString(), F_LQTYBalance_After.toString())
+ })
+
+ it("test share + LQTY fuzzy", async () => {
+ const ammUsers = [u1, u2, u3, u4, u5]
+ const userBalance = [0, 0, 0, 0, 0]
+ const nonAmmUsers = [v1, v2, v3, v4, v5]
+
+ let totalDeposits = 0
+
+ // test almost equal
+ assert(almostTheSame(web3.utils.toWei("9999"), web3.utils.toWei("9999")))
+ assert(! almostTheSame(web3.utils.toWei("9989"), web3.utils.toWei("9999")))
+
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: ammUsers[i] } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: nonAmmUsers[i] } })
+
+ await lusdToken.approve(bamm.address, dec(1000000, 18), { from: ammUsers[i] })
+
+ const qty = toBN(20000)
+ totalDeposits += Number(qty.toString())
+ userBalance[i] += Number(qty.toString())
+ await bamm.deposit(qty, { from: ammUsers[i] })
+ await stabilityPool.provideToSP(qty, frontEnd_1, { from: nonAmmUsers[i] })
+ }
+
+ for(n = 0 ; n < 10 ; n++) {
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR * (i + n + 1), web3.currentProvider)
+ assert(almostTheSame((await lqtyToken.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).toString()))
+ assert.equal((await lusdToken.balanceOf(ammUsers[i])).toString(), (await lusdToken.balanceOf(nonAmmUsers[i])).toString())
+
+ const qty = (i+1) * 1000 + (n+1)*1000 // small number as 0 decimals
+ if((n*7 + i*3) % 2 === 0) {
+ const share = (await bamm.total()).mul(toBN(qty)).div(toBN(totalDeposits))
+ console.log("withdraw", i, {qty}, {totalDeposits}, share.toString())
+ await bamm.withdraw(share.toString(), { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(qty, { from: nonAmmUsers[i] })
+
+ totalDeposits -= qty
+ userBalance[i] -= qty
+ }
+ else {
+ console.log("deposit", i)
+ await bamm.deposit(qty, { from: ammUsers[i]} )
+ await stabilityPool.provideToSP(qty, frontEnd_1, { from: nonAmmUsers[i] })
+
+ totalDeposits += qty
+ userBalance[i] += qty
+ }
+
+ const totalSupply = await bamm.totalSupply()
+ const userSupply = await bamm.balanceOf(ammUsers[i])
+ // userSup / totalSupply = userBalance / totalDeposits
+ assert.equal(userSupply.mul(toBN(totalDeposits)).toString(), toBN(userBalance[i]).mul(totalSupply).toString())
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR * (i + n + 1), web3.currentProvider)
+
+ await bamm.withdraw(0, { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[i] })
+
+ await bamm.withdraw(0, { from: ammUsers[0] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[0] })
+
+ assert.equal((await lusdToken.balanceOf(ammUsers[i])).toString(), (await lusdToken.balanceOf(nonAmmUsers[i])).toString())
+ assert(almostTheSame((await lqtyToken.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).toString()))
+ assert(almostTheSame((await lqtyToken.balanceOf(ammUsers[0])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[0])).toString()))
+ }
+ }
+
+ console.log("get all lqty")
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await bamm.withdraw(0, { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[i] })
+ }
+
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ assert(almostTheSame((await lqtyToken.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).toString()))
+ }
+ })
+
+ it("test complex LQTY allocation", async () => {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+
+ // A, B, C, open troves
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: B } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: D } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: E } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: F } })
+
+ const A_LQTYBalance_Before = await lqtyToken.balanceOf(A)
+ const D_LQTYBalance_Before = await lqtyToken.balanceOf(D)
+ const E_LQTYBalance_Before = await lqtyToken.balanceOf(E)
+ const F_LQTYBalance_Before = await lqtyToken.balanceOf(F)
+
+ assert.equal(A_LQTYBalance_Before, '0')
+ assert.equal(D_LQTYBalance_Before, '0')
+ assert.equal(E_LQTYBalance_Before, '0')
+ assert.equal(F_LQTYBalance_Before, '0')
+
+ // D, E provide to bamm, F provide to SP
+ await lusdToken.approve(bamm.address, dec(1000, 18), { from: D })
+ await lusdToken.approve(bamm.address, dec(2000, 18), { from: E })
+ await lusdToken.approve(bamm.address, dec(3000, 18), { from: F })
+
+ await bamm.deposit(dec(1000, 18), { from: D })
+ await bamm.deposit(dec(2000, 18), { from: E })
+ //await bamm.deposit(dec(3000, 18), { from: F })
+
+ await bamm.withdraw(0, { from: D })
+ console.log((await lqtyToken.balanceOf(D)).toString())
+
+ console.log("share:", (await bamm.share.call()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+
+ await stabilityPool.provideToSP(dec(1000, 18), frontEnd_1, { from: A })
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await bamm.deposit(dec(3000, 18), { from: F })
+ await stabilityPool.provideToSP(dec(3000, 18), frontEnd_1, { from: B })
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ console.log("lqty A", (await lqtyToken.balanceOf(A)).toString())
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ console.log("share:", (await bamm.share()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+ console.log("stake F:", (await bamm.stake(F)).toString())
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ console.log("lqty A", (await lqtyToken.balanceOf(A)).toString())
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ await stabilityPool.withdrawFromSP(0, { from: B })
+ await bamm.withdraw(0, { from: D })
+ await bamm.withdraw(0, { from: E })
+ await bamm.withdraw(0, { from: F })
+
+ console.log("lqty D", (await lqtyToken.balanceOf(D)).toString())
+ console.log("lqty E", (await lqtyToken.balanceOf(E)).toString())
+ console.log("lqty F", (await lqtyToken.balanceOf(F)).toString())
+
+ console.log("share:", (await bamm.share()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+ console.log("stake F:", (await bamm.stake(F)).toString())
+
+ // Get F1, F2, F3 LQTY balances after, and confirm they have increased
+ const A_LQTYBalance_After = await lqtyToken.balanceOf(A)
+ const B_LQTYBalance_After = await lqtyToken.balanceOf(B)
+ const D_LQTYBalance_After = await lqtyToken.balanceOf(D)
+ const E_LQTYBalance_After = await lqtyToken.balanceOf(E)
+ const F_LQTYBalance_After = await lqtyToken.balanceOf(F)
+
+ assert.equal(D_LQTYBalance_After.toString(), A_LQTYBalance_After.toString())
+ assert.equal(E_LQTYBalance_After.toString(), A_LQTYBalance_After.mul(toBN(2)).toString())
+ assert.equal(F_LQTYBalance_After.toString(), B_LQTYBalance_After.toString())
+ })
+
+ it('test share with ether', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+ //console.log(ethGains.toString(), (await stabilityPool.getDepositorETHGain(bamm.address)).toString())
+
+ // send some ETH to simulate partial rebalance
+ await web3.eth.sendTransaction({from: whale, to: bamm.address, value: toBN(dec(1, 18))})
+ assert.equal(toBN(await web3.eth.getBalance(bamm.address)).toString(), toBN(dec(1, 18)).toString())
+
+ const totalEth = ethGains.add(toBN(dec(1, 18)))
+ const totalUsd = toBN(dec(6000, 18)).add(totalEth.mul(toBN(105)))
+
+ await lusdToken.approve(bamm.address, totalUsd, { from: B })
+ await bamm.deposit(totalUsd, { from: B } )
+
+ assert.equal((await bamm.balanceOf(A)).toString(), (await bamm.balanceOf(B)).toString())
+
+ const ethBalanceBefore = toBN(await web3.eth.getBalance(A))
+ const LUSDBefore = await lusdToken.balanceOf(A)
+ await bamm.withdraw(await bamm.balanceOf(A), {from: A, gasPrice: 0})
+ const ethBalanceAfter = toBN(await web3.eth.getBalance(A))
+ const LUSDAfter = await lusdToken.balanceOf(A)
+
+ const withdrawUsdValue = LUSDAfter.sub(LUSDBefore).add((ethBalanceAfter.sub(ethBalanceBefore)).mul(toBN(105)))
+ assert(in100WeiRadius(withdrawUsdValue.toString(), totalUsd.toString()))
+
+ assert(in100WeiRadius("10283999999999999997375", "10283999999999999997322"))
+ assert(! in100WeiRadius("10283999999999999996375", "10283999999999999997322"))
+ })
+
+ it('price exceed max dicount and/or eth balance', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ // without fee
+ await bamm.setParams(20, 0, {from: bammOwner})
+ const price = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(price.ethAmount.toString(), dec(104, 18-2).toString())
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(priceWithFee.ethAmount.toString(), dec(10296, 18-4).toString())
+
+ // without fee
+ await bamm.setParams(20, 0, {from: bammOwner})
+ const priceDepleted = await bamm.getSwapEthAmount(dec(1050000000000000, 18))
+ assert.equal(priceDepleted.ethAmount.toString(), ethGains.toString())
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceDepletedWithFee = await bamm.getSwapEthAmount(dec(1050000000000000, 18))
+ assert.equal(priceDepletedWithFee.ethAmount.toString(), ethGains.mul(toBN(99)).div(toBN(100)))
+ })
+
+ it('test getSwapEthAmount', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ const lusdQty = dec(105, 18)
+ const expectedReturn = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 200)
+
+ // without fee
+ await bamm.setParams(200, 0, {from: bammOwner})
+ const priceWithoutFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithoutFee.ethAmount.toString(), expectedReturn.mul(toBN(100)).div(toBN(100 * 105)).toString())
+
+ // with fee
+ await bamm.setParams(200, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithFee.ethAmount.toString(), expectedReturn.mul(toBN(99)).div(toBN(100 * 105)).toString())
+ })
+
+ it('test fetch price', async () => {
+ await priceFeed.setPrice(dec(666, 18));
+ assert.equal(await bamm.fetchPrice(), dec(666, 18))
+
+ await chainlink.setTimestamp(888)
+ assert.equal((await bamm.fetchPrice()).toString(), "0")
+ })
+
+ it('test swap', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(priceWithFee.ethAmount.toString(), dec(10296, 18-4).toString())
+ assert.equal(priceWithFee.feeEthAmount.toString(), dec(10400 - 10296, 18-4).toString())
+
+ await lusdToken.approve(bamm.address, dec(105,18), {from: whale})
+ const dest = "0xdEADBEEF00AA81bBCF694bC5c05A397F5E5658D5"
+
+ await assertRevert(bamm.swap(dec(105,18), priceWithFee.ethAmount.add(toBN(1)), dest, {from: whale}), 'swap: low return')
+ await bamm.swap(dec(105,18), priceWithFee.ethAmount, dest, {from: whale}) // TODO - check once with higher value so it will revert
+
+ // check lusd balance
+ assert.equal(toBN(dec(6105, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+
+ // check eth balance
+ assert.equal(await web3.eth.getBalance(dest), priceWithFee.ethAmount)
+
+ // check fees
+ assert.equal(await web3.eth.getBalance(feePool), priceWithFee.feeEthAmount)
+ })
+
+ it('test set params happy path', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ const lusdQty = dec(105, 18)
+ const expectedReturn200 = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 200)
+ const expectedReturn190 = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 190)
+
+ assert(expectedReturn200.toString() !== expectedReturn190.toString())
+
+ // without fee
+ await bamm.setParams(200, 0, {from: bammOwner})
+ const priceWithoutFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithoutFee.ethAmount.toString(), expectedReturn200.mul(toBN(100)).div(toBN(100 * 105)).toString())
+
+ // with fee
+ await bamm.setParams(190, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithFee.ethAmount.toString(), expectedReturn190.mul(toBN(99)).div(toBN(100 * 105)).toString())
+ })
+
+ it('test set params sad path', async () => {
+ await assertRevert(bamm.setParams(210, 100, {from: bammOwner}), 'setParams: A too big')
+ await assertRevert(bamm.setParams(10, 100, {from: bammOwner}), 'setParams: A too small')
+ await assertRevert(bamm.setParams(10, 101, {from: bammOwner}), 'setParams: fee is too big')
+ await assertRevert(bamm.setParams(20, 100, {from: B}), 'Ownable: caller is not the owner')
+ })
+
+ it.skip('transfer happy test', async () => { // transfer is not supported anymore
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: D } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+ await stabilityPool.provideToSP(toBN(dec(10000, 18)), frontEnd_1, {from: C})
+
+ assert.equal(await bamm.balanceOf(A), dec(1, 18))
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await stabilityPool.provideToSP(toBN(dec(5000, 18)), frontEnd_1, {from: D})
+
+ await bamm.transfer(B, dec(5, 17), {from: A})
+ assert.equal(await bamm.balanceOf(A), dec(5, 17))
+ assert.equal(await bamm.balanceOf(B), dec(5, 17))
+
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: C })
+ assert.equal(await lqtyToken.balanceOf(B), "0")
+ await bamm.withdraw(0, {from: A})
+ assert.equal((await lqtyToken.balanceOf(A)).toString(), (await lqtyToken.balanceOf(C)).toString())
+
+ // reset A's usd balance
+ await lusdToken.transfer(C, await lusdToken.balanceOf(A), {from: A})
+ assert.equal(await lusdToken.balanceOf(A), "0")
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await bamm.withdraw(toBN(dec(5, 17)), {from: A}) // check balance
+ await bamm.withdraw(toBN(dec(5, 17)), {from: B}) // check balance
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: C })
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: D })
+
+ assert.equal((await lqtyToken.balanceOf(B)).toString(), (await lqtyToken.balanceOf(D)).toString())
+ assert.equal((await lqtyToken.balanceOf(A)).toString(), (await lqtyToken.balanceOf(C)).toString())
+
+ assert.equal((await lusdToken.balanceOf(B)).toString(), dec(5000, 18))
+ assert.equal((await lusdToken.balanceOf(A)).toString(), dec(5000, 18))
+ })
+
+
+ // tests:
+ // 1. complex lqty staking + share V
+ // 2. share test with ether V
+ // 3. basic share with liquidation (withdraw after liquidation) V
+ // 4. price that exceeds max discount V
+ // 5. price that exceeds balance V
+ // 5.5 test fees and return V
+ // 5.6 test swap v
+ // 6.1 test fetch price V
+ // 6. set params V
+ // 7. test with front end v
+ // 8. formula V
+ // 9. lp token - transfer sad test
+ // 11. pickle V
+ // 10. cleanups - compilation warnings. cropjoin - revoke changes and maybe make internal. V
+ // 12 - linter. events
+ })
+})
+
+
+function almostTheSame(n1, n2) {
+ n1 = Number(web3.utils.fromWei(n1))
+ n2 = Number(web3.utils.fromWei(n2))
+ //console.log(n1,n2)
+
+ if(n1 * 1000 > n2 * 1001) return false
+ if(n2 * 1000 > n1 * 1001) return false
+ return true
+}
+
+function in100WeiRadius(n1, n2) {
+ const x = toBN(n1)
+ const y = toBN(n2)
+
+ if(x.add(toBN(100)).lt(y)) return false
+ if(y.add(toBN(100)).lt(x)) return false
+
+ return true
+}
+
+async function assertRevert(txPromise, message = undefined) {
+ try {
+ const tx = await txPromise
+ // console.log("tx succeeded")
+ assert.isFalse(tx.receipt.status) // when this assert fails, the expected revert didn't occur, i.e. the tx succeeded
+ } catch (err) {
+ // console.log("tx failed")
+ assert.include(err.message, "revert")
+
+ if (message) {
+ assert.include(err.message, message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/contracts/test/B.Protocol/PickleTest.js b/packages/contracts/test/B.Protocol/PickleTest.js
new file mode 100644
index 000000000..81893eb34
--- /dev/null
+++ b/packages/contracts/test/B.Protocol/PickleTest.js
@@ -0,0 +1,810 @@
+const deploymentHelper = require("./../../utils/deploymentHelpers.js")
+const testHelpers = require("./../../utils/testHelpers.js")
+const th = testHelpers.TestHelper
+const dec = th.dec
+const toBN = th.toBN
+const mv = testHelpers.MoneyValues
+const timeValues = testHelpers.TimeValues
+
+const TroveManagerTester = artifacts.require("TroveManagerTester")
+const LUSDToken = artifacts.require("LUSDToken")
+const NonPayable = artifacts.require('NonPayable.sol')
+const BAMM = artifacts.require("PBAMM.sol")
+const BLens = artifacts.require("BLens.sol")
+const EIP20 = artifacts.require("EIP20.sol")
+const Pickle = artifacts.require("PickleJar.sol")
+const ChainlinkTestnet = artifacts.require("ChainlinkTestnet.sol")
+
+const ZERO = toBN('0')
+const ZERO_ADDRESS = th.ZERO_ADDRESS
+const maxBytes32 = th.maxBytes32
+
+const getFrontEndTag = async (stabilityPool, depositor) => {
+ return (await stabilityPool.deposits(depositor))[1]
+}
+
+contract('Pickle', async accounts => {
+ const [owner,
+ defaulter_1, defaulter_2, defaulter_3,
+ whale,
+ alice, bob, carol, dennis, erin, flyn,
+ A, B, C, D, E, F,
+ u1, u2, u3, u4, u5,
+ v1, v2, v3, v4, v5,
+ frontEnd_1, frontEnd_2, frontEnd_3,
+ bammOwner
+ ] = accounts;
+
+ const [bountyAddress, lpRewardsAddress, multisig] = accounts.slice(997, 1000)
+
+ const frontEnds = [frontEnd_1, frontEnd_2, frontEnd_3]
+ let contracts
+ let priceFeed
+ let lusdToken
+ let sortedTroves
+ let troveManager
+ let activePool
+ let stabilityPool
+ let bamm
+ let chainlink
+ let defaultPool
+ let borrowerOperations
+ let lqtyToken
+ let communityIssuance
+
+ let gasPriceInWei
+ let pLqty
+ let pJar
+
+ const feePool = "0x1000000000000000000000000000000000000001"
+
+ const getOpenTroveLUSDAmount = async (totalDebt) => th.getOpenTroveLUSDAmount(contracts, totalDebt)
+ const openTrove = async (params) => th.openTrove(contracts, params)
+ //const assertRevert = th.assertRevert
+
+ describe("PBAMM", async () => {
+
+ before(async () => {
+ gasPriceInWei = await web3.eth.getGasPrice()
+ })
+
+ beforeEach(async () => {
+ contracts = await deploymentHelper.deployLiquityCore()
+ contracts.troveManager = await TroveManagerTester.new()
+ contracts.lusdToken = await LUSDToken.new(
+ contracts.troveManager.address,
+ contracts.stabilityPool.address,
+ contracts.borrowerOperations.address
+ )
+ const LQTYContracts = await deploymentHelper.deployLQTYContracts(bountyAddress, lpRewardsAddress, multisig)
+
+ priceFeed = contracts.priceFeedTestnet
+ lusdToken = contracts.lusdToken
+ sortedTroves = contracts.sortedTroves
+ troveManager = contracts.troveManager
+ activePool = contracts.activePool
+ stabilityPool = contracts.stabilityPool
+ defaultPool = contracts.defaultPool
+ borrowerOperations = contracts.borrowerOperations
+ hintHelpers = contracts.hintHelpers
+
+ lqtyToken = LQTYContracts.lqtyToken
+ communityIssuance = LQTYContracts.communityIssuance
+
+ await deploymentHelper.connectLQTYContracts(LQTYContracts)
+ await deploymentHelper.connectCoreContracts(contracts, LQTYContracts)
+ await deploymentHelper.connectLQTYContractsToCore(LQTYContracts, contracts)
+
+ // Register 3 front ends
+ //await th.registerFrontEnds(frontEnds, stabilityPool)
+
+ // deploy BAMM
+ chainlink = await ChainlinkTestnet.new(priceFeed.address)
+
+ const kickbackRate_F1 = toBN(dec(5, 17)) // F1 kicks 50% back to depositor
+ await stabilityPool.registerFrontEnd(kickbackRate_F1, { from: frontEnd_1 })
+
+ pLqty = await EIP20.new(0, "pickle lqty", 18, "pLqty")
+ pJar = await Pickle.new(lqtyToken.address, pLqty.address)
+ bamm = await BAMM.new(chainlink.address, stabilityPool.address, lusdToken.address, lqtyToken.address, 400, feePool, frontEnd_1,
+ pLqty.address, pJar.address, {from: bammOwner})
+ lens = await BLens.new()
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): increases the Stability Pool LUSD balance", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await bamm.deposit(toBN(200), { from: alice })
+
+ // check LUSD balances after
+ const stabilityPool_LUSD_After = await stabilityPool.getTotalLUSDDeposits()
+ assert.equal(stabilityPool_LUSD_After, 200)
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): two users deposit, check their share", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: whale } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await lusdToken.approve(bamm.address, toBN(200), { from: whale })
+ await bamm.deposit(toBN(200), { from: alice })
+ await bamm.deposit(toBN(200), { from: whale })
+
+ // check LUSD balances after1
+ const whaleShare = await bamm.stake(whale)
+ const aliceShare = await bamm.stake(alice)
+
+ assert.equal(whaleShare.toString(), aliceShare.toString())
+ })
+
+ // --- provideToSP() ---
+ // increases recorded LUSD at Stability Pool
+ it("deposit(): two users deposit, one withdraw. check their share", async () => {
+ // --- SETUP --- Give Alice a least 200
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: alice } })
+ await openTrove({ extraLUSDAmount: toBN(200), ICR: toBN(dec(2, 18)), extraParams: { from: whale } })
+
+ // --- TEST ---
+ await lusdToken.approve(bamm.address, toBN(200), { from: alice })
+ await lusdToken.approve(bamm.address, toBN(100), { from: whale })
+ await bamm.deposit(toBN(200), { from: alice })
+ await bamm.deposit(toBN(100), { from: whale })
+
+ // check LUSD balances after1
+ const whaleShare = await bamm.stake(whale)
+ const aliceShare = await bamm.stake(alice)
+
+ assert.equal(whaleShare.mul(toBN(2)).toString(), aliceShare.toString())
+
+ const whaleBalanceBefore = await lusdToken.balanceOf(whale)
+ const shareToWithdraw = whaleShare.div(toBN(2));
+ await bamm.withdraw(shareToWithdraw, { from: whale });
+
+ const newWhaleShare = await bamm.stake(whale)
+ assert.equal(newWhaleShare.mul(toBN(2)).toString(), whaleShare.toString())
+
+ const whaleBalanceAfter = await lusdToken.balanceOf(whale)
+ assert.equal(whaleBalanceAfter.sub(whaleBalanceBefore).toString(), 50)
+ })
+
+ it('rebalance scenario', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ bamm.deposit(whaleLUSD, { from: whale } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+ // Alice makes Trove and withdraws 100 LUSD
+ await openTrove({ extraLUSDAmount: toBN(dec(100, 18)), ICR: toBN(dec(5, 18)), extraParams: { from: alice, value: dec(50, 'ether') } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+ console.log("rebalance", (await bamm.fetchPrice()).toString())
+
+ const SPLUSD_Before = await stabilityPool.getTotalLUSDDeposits()
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // Confirm SP has decreased
+ const SPLUSD_After = await stabilityPool.getTotalLUSDDeposits()
+ assert.isTrue(SPLUSD_After.lt(SPLUSD_Before))
+
+ console.log((await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ console.log((await stabilityPool.getDepositorETHGain(bamm.address)).toString())
+ const price = await priceFeed.fetchPrice.call()
+ console.log(price.toString())
+
+ const ammExpectedEth = await bamm.getSwapEthAmount.call(toBN(dec(1, 18)))
+
+ console.log("expected eth amount", ammExpectedEth.ethAmount.toString())
+
+ const rate = await bamm.getConversionRate(lusdToken.address, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", toBN(dec(1, 18)), 0)
+ assert.equal(rate.toString(), ammExpectedEth.ethAmount.toString())
+
+ await lusdToken.approve(bamm.address, toBN(dec(1, 18)), { from: alice })
+
+ const dest = "0xe1A587Ac322da1611DF55b11A6bC8c6052D896cE" // dummy address
+ //await bamm.swap(toBN(dec(1, 18)), dest, { from: alice })
+ await bamm.trade(lusdToken.address, toBN(dec(1, 18)), "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", dest, rate, true, { from: alice });
+
+ const swapBalance = await web3.eth.getBalance(dest)
+
+ assert.equal(swapBalance, ammExpectedEth.ethAmount)
+ })
+
+ it("test basic LQTY allocation", async () => {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+
+ // A, B, C, open troves
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: B } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: D } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: E } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: F } })
+
+ // D, E provide to bamm, F provide to SP
+ await lusdToken.approve(bamm.address, dec(1000, 18), { from: D })
+ await lusdToken.approve(bamm.address, dec(2000, 18), { from: E })
+ await bamm.deposit(dec(1000, 18), { from: D })
+ await bamm.deposit(dec(2000, 18), { from: E })
+ await stabilityPool.provideToSP(dec(3000, 18), frontEnd_1, { from: F })
+
+ // Get F1, F2, F3 LQTY balances before, and confirm they're zero
+ const D_LQTYBalance_Before = await pLqty.balanceOf(D)
+ const E_LQTYBalance_Before = await pLqty.balanceOf(E)
+ const F_LQTYBalance_Before = await lqtyToken.balanceOf(F)
+
+ assert.equal(D_LQTYBalance_Before, '0')
+ assert.equal(E_LQTYBalance_Before, '0')
+ assert.equal(F_LQTYBalance_Before, '0')
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ const expectdDLqtyDelta = await lens.getUnclaimedLqty.call(D, bamm.address, pLqty.address)
+ const expectdELqtyDelta = await lens.getUnclaimedLqty.call(E, bamm.address, pLqty.address)
+
+ await stabilityPool.withdrawFromSP(0, { from: F })
+ await bamm.withdraw(0, { from: D })
+ await bamm.withdraw(0, { from: E })
+
+ // Get F1, F2, F3 LQTY balances after, and confirm they have increased
+ const D_LQTYBalance_After = await pLqty.balanceOf(D)
+ const E_LQTYBalance_After = await pLqty.balanceOf(E)
+ const F_LQTYBalance_After = await lqtyToken.balanceOf(F)
+
+ assert((await lqtyToken.balanceOf(frontEnd_1)).gt(toBN(0)))
+ assert((await pLqty.balanceOf(D)).gt(toBN(0)))
+ assert((await pLqty.balanceOf(E)).gt(toBN(0)))
+
+ assert.equal(D_LQTYBalance_After.sub(D_LQTYBalance_Before).toString(), expectdDLqtyDelta.toString())
+ assert.equal(E_LQTYBalance_After.sub(E_LQTYBalance_Before).toString(), expectdELqtyDelta.toString())
+
+ assert.equal(D_LQTYBalance_After.add(E_LQTYBalance_After).toString(), F_LQTYBalance_After.div(toBN(2)).toString())
+ })
+
+ it("test share + LQTY fuzzy", async () => {
+ const ammUsers = [u1, u2, u3, u4, u5]
+ const userBalance = [0, 0, 0, 0, 0]
+ const nonAmmUsers = [v1, v2, v3, v4, v5]
+
+ let totalDeposits = 0
+
+ // test almost equal
+ assert(almostTheSame(web3.utils.toWei("9999"), web3.utils.toWei("9999")))
+ assert(! almostTheSame(web3.utils.toWei("9989"), web3.utils.toWei("9999")))
+
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: ammUsers[i] } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: nonAmmUsers[i] } })
+
+ await lusdToken.approve(bamm.address, dec(1000000, 18), { from: ammUsers[i] })
+
+ const qty = toBN(20000)
+ totalDeposits += Number(qty.toString())
+ userBalance[i] += Number(qty.toString())
+ await bamm.deposit(qty, { from: ammUsers[i] })
+ await stabilityPool.provideToSP(qty, frontEnd_1, { from: nonAmmUsers[i] })
+ }
+
+ for(n = 0 ; n < 10 ; n++) {
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR * (i + n + 1), web3.currentProvider)
+ assert(almostTheSame((await pLqty.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).div(toBN(2)).toString()))
+ assert.equal((await lusdToken.balanceOf(ammUsers[i])).toString(), (await lusdToken.balanceOf(nonAmmUsers[i])).toString())
+
+ const qty = (i+1) * 1000 + (n+1)*1000 // small number as 0 decimals
+ if((n*7 + i*3) % 2 === 0) {
+ const share = (await bamm.total()).mul(toBN(qty)).div(toBN(totalDeposits))
+ console.log("withdraw", i, {qty}, {totalDeposits}, share.toString())
+ await bamm.withdraw(share.toString(), { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(qty, { from: nonAmmUsers[i] })
+
+ totalDeposits -= qty
+ userBalance[i] -= qty
+ }
+ else {
+ console.log("deposit", i)
+ await bamm.deposit(qty, { from: ammUsers[i]} )
+ await stabilityPool.provideToSP(qty, frontEnd_1, { from: nonAmmUsers[i] })
+
+ totalDeposits += qty
+ userBalance[i] += qty
+ }
+
+ const totalSupply = await bamm.totalSupply()
+ const userSupply = await bamm.balanceOf(ammUsers[i])
+ // userSup / totalSupply = userBalance / totalDeposits
+ assert.equal(userSupply.mul(toBN(totalDeposits)).toString(), toBN(userBalance[i]).mul(totalSupply).toString())
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR * (i + n + 1), web3.currentProvider)
+
+ await bamm.withdraw(0, { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[i] })
+
+ await bamm.withdraw(0, { from: ammUsers[0] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[0] })
+
+ assert.equal((await lusdToken.balanceOf(ammUsers[i])).toString(), (await lusdToken.balanceOf(nonAmmUsers[i])).toString())
+ assert(almostTheSame((await pLqty.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).div(toBN(2)).toString()))
+ assert(almostTheSame((await pLqty.balanceOf(ammUsers[0])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[0])).div(toBN(2)).toString()))
+ }
+ }
+
+ console.log("get all lqty")
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ await bamm.withdraw(0, { from: ammUsers[i] })
+ await stabilityPool.withdrawFromSP(0, { from: nonAmmUsers[i] })
+ }
+
+ for(let i = 0 ; i < ammUsers.length ; i++) {
+ assert(almostTheSame((await pLqty.balanceOf(ammUsers[i])).toString(), (await lqtyToken.balanceOf(nonAmmUsers[i])).div(toBN(2)).toString()))
+ }
+ })
+
+ it("test complex LQTY allocation", async () => {
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(10, 18)), extraParams: { from: whale } })
+
+ // A, B, C, open troves
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: B } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(1000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: D } })
+ await openTrove({ extraLUSDAmount: toBN(dec(2000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: E } })
+ await openTrove({ extraLUSDAmount: toBN(dec(3000, 18)), ICR: toBN(dec(2, 18)), extraParams: { from: F } })
+
+ const A_LQTYBalance_Before = await lqtyToken.balanceOf(A)
+ const D_LQTYBalance_Before = await lqtyToken.balanceOf(D)
+ const E_LQTYBalance_Before = await pLqty.balanceOf(E)
+ const F_LQTYBalance_Before = await pLqty.balanceOf(F)
+
+ assert.equal(A_LQTYBalance_Before, '0')
+ assert.equal(D_LQTYBalance_Before, '0')
+ assert.equal(E_LQTYBalance_Before, '0')
+ assert.equal(F_LQTYBalance_Before, '0')
+
+ // D, E provide to bamm, F provide to SP
+ await lusdToken.approve(bamm.address, dec(1000, 18), { from: D })
+ await lusdToken.approve(bamm.address, dec(2000, 18), { from: E })
+ await lusdToken.approve(bamm.address, dec(3000, 18), { from: F })
+
+ await bamm.deposit(dec(1000, 18), { from: D })
+ await bamm.deposit(dec(2000, 18), { from: E })
+ //await bamm.deposit(dec(3000, 18), { from: F })
+
+ await bamm.withdraw(0, { from: D })
+ console.log((await lqtyToken.balanceOf(D)).toString())
+
+ console.log("share:", (await bamm.share.call()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+
+ await stabilityPool.provideToSP(dec(1000, 18), frontEnd_1, { from: A })
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await bamm.deposit(dec(3000, 18), { from: F })
+ await stabilityPool.provideToSP(dec(3000, 18), frontEnd_1, { from: B })
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ console.log("lqty A", (await lqtyToken.balanceOf(A)).toString())
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ console.log("share:", (await bamm.share()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+ console.log("stake F:", (await bamm.stake(F)).toString())
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ console.log("lqty A", (await lqtyToken.balanceOf(A)).toString())
+
+ await stabilityPool.withdrawFromSP(0, { from: A })
+ await stabilityPool.withdrawFromSP(0, { from: B })
+ await bamm.withdraw(0, { from: D })
+ await bamm.withdraw(0, { from: E })
+ await bamm.withdraw(0, { from: F })
+
+ console.log("lqty D", (await pLqty.balanceOf(D)).toString())
+ console.log("lqty E", (await pLqty.balanceOf(E)).toString())
+ console.log("lqty F", (await pLqty.balanceOf(F)).toString())
+
+ console.log("share:", (await bamm.share()).toString())
+ console.log("stake D:", (await bamm.stake(D)).toString())
+ console.log("stake E:", (await bamm.stake(E)).toString())
+ console.log("stake F:", (await bamm.stake(F)).toString())
+
+ // Get F1, F2, F3 LQTY balances after, and confirm they have increased
+ const A_LQTYBalance_After = await lqtyToken.balanceOf(A)
+ const B_LQTYBalance_After = await lqtyToken.balanceOf(B)
+ const D_LQTYBalance_After = await pLqty.balanceOf(D)
+ const E_LQTYBalance_After = await pLqty.balanceOf(E)
+ const F_LQTYBalance_After = await pLqty.balanceOf(F)
+
+ assert.equal(D_LQTYBalance_After.toString(), A_LQTYBalance_After.div(toBN(2)).toString())
+ assert.equal(E_LQTYBalance_After.toString(), A_LQTYBalance_After.div(toBN(2)).mul(toBN(2)).toString())
+ assert.equal(F_LQTYBalance_After.toString(), B_LQTYBalance_After.div(toBN(2)).toString())
+ })
+
+ it('test share with ether', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+ //console.log(ethGains.toString(), (await stabilityPool.getDepositorETHGain(bamm.address)).toString())
+
+ // send some ETH to simulate partial rebalance
+ await web3.eth.sendTransaction({from: whale, to: bamm.address, value: toBN(dec(1, 18))})
+ assert.equal(toBN(await web3.eth.getBalance(bamm.address)).toString(), toBN(dec(1, 18)).toString())
+
+ const totalEth = ethGains.add(toBN(dec(1, 18)))
+ const totalUsd = toBN(dec(6000, 18)).add(totalEth.mul(toBN(105)))
+
+ await lusdToken.approve(bamm.address, totalUsd, { from: B })
+ await bamm.deposit(totalUsd, { from: B } )
+
+ assert.equal((await bamm.balanceOf(A)).toString(), (await bamm.balanceOf(B)).toString())
+
+ const ethBalanceBefore = toBN(await web3.eth.getBalance(A))
+ const LUSDBefore = await lusdToken.balanceOf(A)
+ await bamm.withdraw(await bamm.balanceOf(A), {from: A, gasPrice: 0})
+ const ethBalanceAfter = toBN(await web3.eth.getBalance(A))
+ const LUSDAfter = await lusdToken.balanceOf(A)
+
+ const withdrawUsdValue = LUSDAfter.sub(LUSDBefore).add((ethBalanceAfter.sub(ethBalanceBefore)).mul(toBN(105)))
+ assert(in100WeiRadius(withdrawUsdValue.toString(), totalUsd.toString()))
+
+ assert(in100WeiRadius("10283999999999999997375", "10283999999999999997322"))
+ assert(! in100WeiRadius("10283999999999999996375", "10283999999999999997322"))
+ })
+
+ it('price exceed max dicount and/or eth balance', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ // without fee
+ await bamm.setParams(20, 0, {from: bammOwner})
+ const price = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(price.ethAmount.toString(), dec(104, 18-2).toString())
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(priceWithFee.ethAmount.toString(), dec(10296, 18-4).toString())
+
+ // without fee
+ await bamm.setParams(20, 0, {from: bammOwner})
+ const priceDepleted = await bamm.getSwapEthAmount(dec(1050000000000000, 18))
+ assert.equal(priceDepleted.ethAmount.toString(), ethGains.toString())
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceDepletedWithFee = await bamm.getSwapEthAmount(dec(1050000000000000, 18))
+ assert.equal(priceDepletedWithFee.ethAmount.toString(), ethGains.mul(toBN(99)).div(toBN(100)))
+ })
+
+ it('test getSwapEthAmount', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ const lusdQty = dec(105, 18)
+ const expectedReturn = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 200)
+
+ // without fee
+ await bamm.setParams(200, 0, {from: bammOwner})
+ const priceWithoutFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithoutFee.ethAmount.toString(), expectedReturn.mul(toBN(100)).div(toBN(100 * 105)).toString())
+
+ // with fee
+ await bamm.setParams(200, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithFee.ethAmount.toString(), expectedReturn.mul(toBN(99)).div(toBN(100 * 105)).toString())
+ })
+
+ it('test fetch price', async () => {
+ await priceFeed.setPrice(dec(666, 18));
+ assert.equal(await bamm.fetchPrice(), dec(666, 18))
+
+ await chainlink.setTimestamp(888)
+ assert.equal((await bamm.fetchPrice()).toString(), "0")
+ })
+
+ it('test swap', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ // with fee
+ await bamm.setParams(20, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(dec(105, 18))
+ assert.equal(priceWithFee.ethAmount.toString(), dec(10296, 18-4).toString())
+ assert.equal(priceWithFee.feeEthAmount.toString(), dec(10400 - 10296, 18-4).toString())
+
+ await lusdToken.approve(bamm.address, dec(105,18), {from: whale})
+ const dest = "0xdEADBEEF00AA81bBCF694bC5c05A397F5E5658D5"
+ await bamm.swap(dec(105,18), priceWithFee.ethAmount, dest, {from: whale})
+
+ // check lusd balance
+ assert.equal(toBN(dec(6105, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+
+ // check eth balance
+ assert.equal(await web3.eth.getBalance(dest), priceWithFee.ethAmount)
+
+ // check fees
+ assert.equal(await web3.eth.getBalance(feePool), priceWithFee.feeEthAmount)
+ })
+
+ it('test set params happy path', async () => {
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: B } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+
+ // 2 Troves opened, each withdraws minimum debt
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1, } })
+ await openTrove({ extraLUSDAmount: 0, ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_2, } })
+
+
+ // price drops: defaulter's Troves fall below MCR, whale doesn't
+ await priceFeed.setPrice(dec(105, 18));
+
+ // Troves are closed
+ await troveManager.liquidate(defaulter_1, { from: owner })
+ await troveManager.liquidate(defaulter_2, { from: owner })
+
+ // 4k liquidations
+ assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString())
+ const ethGains = web3.utils.toBN("39799999999999999975")
+
+ const lusdQty = dec(105, 18)
+ const expectedReturn200 = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 200)
+ const expectedReturn190 = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 190)
+
+ assert(expectedReturn200.toString() !== expectedReturn190.toString())
+
+ // without fee
+ await bamm.setParams(200, 0, {from: bammOwner})
+ const priceWithoutFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithoutFee.ethAmount.toString(), expectedReturn200.mul(toBN(100)).div(toBN(100 * 105)).toString())
+
+ // with fee
+ await bamm.setParams(190, 100, {from: bammOwner})
+ const priceWithFee = await bamm.getSwapEthAmount(lusdQty)
+ assert.equal(priceWithFee.ethAmount.toString(), expectedReturn190.mul(toBN(99)).div(toBN(100 * 105)).toString())
+ })
+
+ it('test set params sad path', async () => {
+ await assertRevert(bamm.setParams(210, 100, {from: bammOwner}), 'setParams: A too big')
+ await assertRevert(bamm.setParams(10, 100, {from: bammOwner}), 'setParams: A too small')
+ await assertRevert(bamm.setParams(10, 101, {from: bammOwner}), 'setParams: fee is too big')
+ await assertRevert(bamm.setParams(20, 100, {from: B}), 'Ownable: caller is not the owner')
+ })
+
+ it.skip('transfer happy test', async () => { // transfer is not supported anymore
+ // --- SETUP ---
+
+ // Whale opens Trove and deposits to SP
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: whale, value: dec(50, 'ether') } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: A } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: C } })
+ await openTrove({ extraLUSDAmount: toBN(dec(10000, 18)), ICR: toBN(dec(20, 18)), extraParams: { from: D } })
+
+ const whaleLUSD = await lusdToken.balanceOf(whale)
+ await lusdToken.approve(bamm.address, whaleLUSD, { from: whale })
+ await lusdToken.approve(bamm.address, toBN(dec(10000, 18)), { from: A })
+ await bamm.deposit(toBN(dec(10000, 18)), { from: A } )
+ await stabilityPool.provideToSP(toBN(dec(10000, 18)), frontEnd_1, {from: C})
+
+ assert.equal(await bamm.balanceOf(A), dec(1, 18))
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await stabilityPool.provideToSP(toBN(dec(5000, 18)), frontEnd_1, {from: D})
+
+ await bamm.transfer(B, dec(5, 17), {from: A})
+ assert.equal(await bamm.balanceOf(A), dec(5, 17))
+ assert.equal(await bamm.balanceOf(B), dec(5, 17))
+
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: C })
+ assert.equal(await pLqty.balanceOf(B), "0")
+ await bamm.withdraw(0, {from: A})
+ assert.equal((await pLqty.balanceOf(A)).toString(), (await lqtyToken.balanceOf(C)).div(toBN(2)).toString())
+
+ // reset A's usd balance
+ await lusdToken.transfer(C, await lusdToken.balanceOf(A), {from: A})
+ assert.equal(await lusdToken.balanceOf(A), "0")
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ await bamm.withdraw(toBN(dec(5, 17)), {from: A}) // check balance
+ await bamm.withdraw(toBN(dec(5, 17)), {from: B}) // check balance
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: C })
+ await stabilityPool.withdrawFromSP(toBN(dec(5000, 18)), { from: D })
+
+ assert.equal((await pLqty.balanceOf(B)).toString(), (await lqtyToken.balanceOf(D)).div(toBN(2)).toString())
+ assert.equal((await pLqty.balanceOf(A)).toString(), (await lqtyToken.balanceOf(C)).div(toBN(2)).toString())
+
+ assert.equal((await lusdToken.balanceOf(B)).toString(), dec(5000, 18))
+ assert.equal((await lusdToken.balanceOf(A)).toString(), dec(5000, 18))
+
+ await th.fastForwardTime(timeValues.SECONDS_IN_ONE_HOUR, web3.currentProvider)
+
+ // TODO check lqty now
+ })
+
+ // tests:
+ // 1. complex lqty staking + share V
+ // 2. share test with ether V
+ // 3. basic share with liquidation (withdraw after liquidation) V
+ // 4. price that exceeds max discount V
+ // 5. price that exceeds balance V
+ // 5.5 test fees and return V
+ // 5.6 test swap v
+ // 6.1 test fetch price V
+ // 6. set params V
+ // 7. test with front end v
+ // 8. formula V
+ // 9. lp token
+ // 11. pickle
+ // 10. cleanups - compilation warnings. cropjoin - revoke changes and maybe make internal.
+ })
+})
+
+
+function almostTheSame(n1, n2) {
+ n1 = Number(web3.utils.fromWei(n1))
+ n2 = Number(web3.utils.fromWei(n2))
+ //console.log(n1,n2)
+
+ if(n1 * 1000 > n2 * 1001) return false
+ if(n2 * 1000 > n1 * 1001) return false
+ return true
+}
+
+function in100WeiRadius(n1, n2) {
+ const x = toBN(n1)
+ const y = toBN(n2)
+
+ if(x.add(toBN(100)).lt(y)) return false
+ if(y.add(toBN(100)).lt(x)) return false
+
+ return true
+}
+
+async function assertRevert(txPromise, message = undefined) {
+ try {
+ const tx = await txPromise
+ // console.log("tx succeeded")
+ assert.isFalse(tx.receipt.status) // when this assert fails, the expected revert didn't occur, i.e. the tx succeeded
+ } catch (err) {
+ // console.log("tx failed")
+ assert.include(err.message, "revert")
+
+ if (message) {
+ assert.include(err.message, message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/contracts/test/B.Protocol/PriceFormulaTest.js b/packages/contracts/test/B.Protocol/PriceFormulaTest.js
new file mode 100644
index 000000000..9df5959ba
--- /dev/null
+++ b/packages/contracts/test/B.Protocol/PriceFormulaTest.js
@@ -0,0 +1,88 @@
+const Decimal = require("decimal.js");
+const { BNConverter } = require("./../../utils/BNConverter.js")
+const testHelpers = require("./../../utils/testHelpers.js")
+const PriceFormula = artifacts.require("./PriceFormula.sol")
+
+const th = testHelpers.TestHelper
+const timeValues = testHelpers.TimeValues
+const dec = th.dec
+const toBN = th.toBN
+const getDifference = th.getDifference
+
+contract('PriceFormula tests', async accounts => {
+ let priceFormula
+
+ before(async () => {
+ priceFormula = await PriceFormula.new()
+ })
+
+ // numbers here were taken from the return value of mainnet contract
+
+ it("check price 0", async () => {
+ const xQty = "1234567891"
+ const xBalance = "321851652450"
+ const yBalance = "219413622039"
+ const A = 200
+ const ret = await priceFormula.getReturn(xQty, xBalance, yBalance, A);
+ const retAfterFee = ret.sub(ret.mul(toBN(4000000)).div(toBN(10**10)))
+ assert.equal(retAfterFee.toString(10), '1231543859')
+ })
+
+ it("fuzzy", async () => {
+ const A = 3
+ const aStep = 7
+ const xQty = "1234567891"
+ const xBalance = "321851652450"
+ const yBalance = "219413622039"
+
+ const As = []
+ const xQtys = []
+ const xBalances = []
+ const yBalances = []
+
+ const excpectedResult = [1188895769, 2411018031, 3638812385, 4868601476, 6099325960, 7330566424, 8562123398, 9793889769, 11025802785, 12257823179, 13489925074, 14722090684, 15954307349, 17186565794, 18418859045, 19651181742, 20883529689, 22115899540, 23348288590, 24580694617, 25813115778, 27045550524, 28277997542, 29510455706, 30742924044, 31975401710, 33207887963, 34440382148, 35672883685, 36905392053, 38137906789, 39370427472, 40602953723, 41835485196, 43068021577, 44300562576, 45533107928, 46765657391, 47998210737, 49230767758, 50463328261, 51695892065, 52928459002, 54161028915, 55393601657, 56626177090, 57858755085, 59091335520, 60323918281, 61556503261, 62789090357, 64021679473, 65254270518, 66486863407, 67719458057, 68952054392, 70184652337, 71417251823, 72649852784, 73882455156, 75115058879, 76347663896, 77580270153, 78812877597, 80045486178, 81278095850, 82510706566, 83743318283, 84975930960, 86208544556, 87441159035, 88673774360, 89906390495, 91139007408, 92371625066, 93604243438, 94836862496, 96069482210, 97302102554, 98534723502, 99767345029, 100999967109, 102232589722, 103465212843, 104697836453, 105930460530, 107163085054, 108395710007, 109628335370, 110860961126, 112093587257, 113326213748, 114558840582, 115791467745, 117024095222, 118256722999, 119489351063, 120721979399, 121954607997, 123187236843]
+
+ assert(almost("123456", "123456"))
+ assert(almost("123455", "123456"))
+ assert(almost("123455", "123454"))
+ assert(!almost("123455", "123453"))
+ assert(!almost("123451", "123453"))
+
+ for(let i = 0 ; i < 100 ; i++) {
+ const newA = A + aStep*(i+1)
+ const qty = web3.utils.toBN(xQty).mul(toBN(i+1))
+ const xbalance = web3.utils.toBN(xBalance).add(qty.mul(toBN(3)))
+ const ybalance = web3.utils.toBN(yBalance).add(qty)
+
+ console.log(newA.toString(), qty.toString(), xbalance.toString(), ybalance.toString())
+
+ console.log(i)
+ const ret = await priceFormula.getReturn(qty.toString(), xbalance.toString(), ybalance.toString(), newA);
+ console.log(ret.toString(), excpectedResult[i], Number(web3.utils.fromWei(ret.toString())) - Number(web3.utils.fromWei(excpectedResult[i].toString())))
+ assert(almost(ret, excpectedResult[i]))
+ //assert.equal(ret.toString(), (excpectedResult[i] - 1).toString())
+
+ As.push(newA)
+ xQtys.push(qty.toString())
+ xBalances.push(xbalance.toString())
+ yBalances.push(ybalance.toString())
+ }
+
+ //console.log("A = [", As.toString(), "]")
+ //console.log("dx = [", xQtys.toString(), "]")
+ //console.log("x = [", xBalances.toString(), "]")
+ //console.log("y = [", yBalances.toString(), "]")
+ })
+})
+
+function almost(n1, n2) {
+ const x = toBN(n1)
+ const y = toBN(n2)
+
+ if(x.toString() === y.toString()) return true
+ if(x.add(toBN(1)).toString() === y.toString()) return true
+ if(y.add(toBN(1)).toString() === x.toString()) return true
+
+ return false
+}
+
diff --git a/packages/dev-frontend/.gitignore b/packages/dev-frontend/.gitignore
index 70e9603ba..ed661a0cb 100644
--- a/packages/dev-frontend/.gitignore
+++ b/packages/dev-frontend/.gitignore
@@ -2,3 +2,8 @@
/build
/.env
/config.json
+
+# CF worker site
+#===========================
+wrangler.toml
+/workers-site
\ No newline at end of file
diff --git a/packages/dev-frontend/package.json b/packages/dev-frontend/package.json
index 4cf87fed1..1205bfe41 100644
--- a/packages/dev-frontend/package.json
+++ b/packages/dev-frontend/package.json
@@ -47,7 +47,9 @@
"start-demo": "cross-env REACT_APP_DEMO_MODE=true run-s start",
"build": "run-s build:*",
"build:set-version": "node scripts/set-version.js",
+ "build:remove-iframe-deny": "node scripts/remove-iframe-deny.js",
"build:react": "react-scripts build",
+ "build:static": "./scripts/generate-static.sh",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
@@ -65,5 +67,8 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@cloudflare/kv-asset-handler": "^0.1.3"
}
}
diff --git a/packages/dev-frontend/public/bprotocol/group-1.png b/packages/dev-frontend/public/bprotocol/group-1.png
new file mode 100644
index 000000000..050b09ab9
Binary files /dev/null and b/packages/dev-frontend/public/bprotocol/group-1.png differ
diff --git a/packages/dev-frontend/public/bprotocol/group-2.png b/packages/dev-frontend/public/bprotocol/group-2.png
new file mode 100644
index 000000000..45aa499c2
Binary files /dev/null and b/packages/dev-frontend/public/bprotocol/group-2.png differ
diff --git a/packages/dev-frontend/public/bprotocol/group-3.png b/packages/dev-frontend/public/bprotocol/group-3.png
new file mode 100644
index 000000000..ae401974e
Binary files /dev/null and b/packages/dev-frontend/public/bprotocol/group-3.png differ
diff --git a/packages/dev-frontend/public/bprotocol/group.png b/packages/dev-frontend/public/bprotocol/group.png
new file mode 100644
index 000000000..6918ce152
Binary files /dev/null and b/packages/dev-frontend/public/bprotocol/group.png differ
diff --git a/packages/dev-frontend/public/bprotocol/icon-a-1.svg b/packages/dev-frontend/public/bprotocol/icon-a-1.svg
new file mode 100644
index 000000000..aad64d9a3
--- /dev/null
+++ b/packages/dev-frontend/public/bprotocol/icon-a-1.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/dev-frontend/public/bprotocol/icon-a-2.svg b/packages/dev-frontend/public/bprotocol/icon-a-2.svg
new file mode 100644
index 000000000..eb8238c61
--- /dev/null
+++ b/packages/dev-frontend/public/bprotocol/icon-a-2.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/dev-frontend/public/bprotocol/icon-a-3.svg b/packages/dev-frontend/public/bprotocol/icon-a-3.svg
new file mode 100644
index 000000000..228ab5631
--- /dev/null
+++ b/packages/dev-frontend/public/bprotocol/icon-a-3.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/dev-frontend/public/favicon.ico b/packages/dev-frontend/public/favicon.ico
new file mode 100644
index 000000000..65cb2c890
--- /dev/null
+++ b/packages/dev-frontend/public/favicon.ico
@@ -0,0 +1 @@
+F40D6CE7-4B76-44BF-B583-679B75EAE8C5 Created with sketchtool.
\ No newline at end of file
diff --git a/packages/dev-frontend/public/favicon.png b/packages/dev-frontend/public/favicon.png
deleted file mode 100644
index 8855560a7..000000000
Binary files a/packages/dev-frontend/public/favicon.png and /dev/null differ
diff --git a/packages/dev-frontend/public/favicon.svg b/packages/dev-frontend/public/favicon.svg
new file mode 100644
index 000000000..65cb2c890
--- /dev/null
+++ b/packages/dev-frontend/public/favicon.svg
@@ -0,0 +1 @@
+F40D6CE7-4B76-44BF-B583-679B75EAE8C5 Created with sketchtool.
\ No newline at end of file
diff --git a/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.eot b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.eot
new file mode 100644
index 000000000..663c783dc
Binary files /dev/null and b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.eot differ
diff --git a/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.ttf b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.ttf
new file mode 100644
index 000000000..856e4f226
Binary files /dev/null and b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.ttf differ
diff --git a/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff
new file mode 100644
index 000000000..9de7cb556
Binary files /dev/null and b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff differ
diff --git a/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff2 b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff2
new file mode 100644
index 000000000..d9182cb71
Binary files /dev/null and b/packages/dev-frontend/public/fonts/NeueHaasGroteskDisp Pro Md.woff2 differ
diff --git a/packages/dev-frontend/public/index.html b/packages/dev-frontend/public/index.html
index 0e8e4800a..6616897bf 100644
--- a/packages/dev-frontend/public/index.html
+++ b/packages/dev-frontend/public/index.html
@@ -2,7 +2,19 @@
-
+
+
+
{
}}
>
- Liquity is not yet deployed to{" "}
+ B.Protocol AMM is not yet deployed to{" "}
{chainId === 1 ? "mainnet" : "this network"}.
- Please switch to Ropsten, Rinkeby, Kovan or Görli.
+ Please switch to Kovan.
);
diff --git a/packages/dev-frontend/src/components/ConnectPage.tsx b/packages/dev-frontend/src/components/ConnectPage.tsx
new file mode 100644
index 000000000..160d77518
--- /dev/null
+++ b/packages/dev-frontend/src/components/ConnectPage.tsx
@@ -0,0 +1,247 @@
+import React from "react";
+import { Flex, Link, Box } from "theme-ui";
+
+type ConnectPageProps = {
+};
+
+type ItemProps = {
+ icon: string,
+ title: React.ReactElement,
+ text: string,
+ link: any
+};
+
+export const device = {
+ mobile: 300,
+ tablet: 700,
+ laptop: 1000,
+ desktop: 1200
+}
+
+const Item: React.FC = ({icon, title, text, link}) => {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {text}
+ {" "}
+ here.
+
+
+ )
+}
+
+
+
+export const ConnectPage: React.FC = ({children}) => {
+ const GROUP = "./bprotocol/group.png"
+ const GROUP_1 = "./bprotocol/group-1.png"
+ const GROUP_2 = "./bprotocol/group-2.png"
+ const GROUP_3 = "./bprotocol/group-3.png"
+ return (
+
+
+
+ Automated Rebalancing for Liquity Stability Pool
+
+
+ Powered by B.Protocol v2
+
+
+
+ {children}
+
+
+ - Stabilize
Liquity Protocol}
+ text="B.Protocol v2 and its novel Backstop AMM (B.AMM) automates the rebalancing of Liquity Stability Pool to maintain its strength.
+ Read more on how the Liquity
+ SP is working "
+ link="https://docs.liquity.org/faq/stability-pool-and-liquidations"
+ />
+ - Get Passive
+ Yield on Your LUSD}
+ text="By using B.Protocol to deposit your LUSD into Liquity Stability Pool, you can save the manual operation of selling your accumulated ETH back to LUSD every time a liquidation is taking place.
+ Read more about how it’s done"
+ link="https://medium.com/b-protocol/b-protocol-liquity-integration-is-live-1342605e7cfb"
+ />
+ - Using
+ B.Protocl V2}
+ text="The integration of Liqity with B.Protocol v2 is a step forward towards a more stabilized DeFi ecosystem.
+ Read more about the novel B.AMM design that enables that"
+ link="https://medium.com/b-protocol/b-amm-efficient-automated-market-maker-for-defi-liquidations-fea7b0fdc0c5"
+ />
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/dev-frontend/src/components/Header.tsx b/packages/dev-frontend/src/components/Header.tsx
index 512d9d0fe..8c41528e2 100644
--- a/packages/dev-frontend/src/components/Header.tsx
+++ b/packages/dev-frontend/src/components/Header.tsx
@@ -26,7 +26,7 @@ export const Header: React.FC = ({ children }) => {
-
+
{
borderLeft: ["none", "1px solid lightgrey"]
}}
/>
+
{isFrontendRegistered && (
<>
-
+
+
+
>
)}
diff --git a/packages/dev-frontend/src/components/Nav.tsx b/packages/dev-frontend/src/components/Nav.tsx
index f082d94b5..595b90e1d 100644
--- a/packages/dev-frontend/src/components/Nav.tsx
+++ b/packages/dev-frontend/src/components/Nav.tsx
@@ -5,14 +5,14 @@ export const Nav: React.FC = () => {
return (
- Dashboard
- Farm
+ Dashboard
+ Farm
-
+
Risky Troves
-
+
Redemption
diff --git a/packages/dev-frontend/src/components/Stability/ActiveDeposit.tsx b/packages/dev-frontend/src/components/Stability/ActiveDeposit.tsx
index 77327210d..fa604c91b 100644
--- a/packages/dev-frontend/src/components/Stability/ActiveDeposit.tsx
+++ b/packages/dev-frontend/src/components/Stability/ActiveDeposit.tsx
@@ -26,7 +26,7 @@ export const ActiveDeposit: React.FC = () => {
const { dispatchEvent } = useStabilityView();
const { stabilityDeposit, trove, lusdInStabilityPool } = useLiquitySelector(selector);
- const poolShare = stabilityDeposit.currentLUSD.mulDiv(100, lusdInStabilityPool);
+ const {poolShare, bammPoolShare} = stabilityDeposit
const handleAdjustDeposit = useCallback(() => {
dispatchEvent("ADJUST_DEPOSIT_PRESSED");
@@ -48,6 +48,8 @@ export const ActiveDeposit: React.FC = () => {
}
}, [transactionState.type, dispatchEvent]);
+ const ethDiffInUsd = stabilityDeposit.currentUSD.sub(stabilityDeposit.currentLUSD)
+ const ethIsImportant = (ethDiffInUsd.div(stabilityDeposit.currentUSD)).gt(1/1000)
return (
@@ -63,9 +65,34 @@ export const ActiveDeposit: React.FC = () => {
+
+
+ {ethIsImportant &&
+
+ Temporary ETH balance until rebalance takes place
+
+ }
+ />
+ }
+ />
+ }
+
{
amount={poolShare.prettify(4)}
unit="%"
/>
-
-
-
+
+
+
{
Adjust
- Claim ETH and LQTY
+ Claim LQTY
- {hasTrove && (
- Claim LQTY and move ETH to Trove
- )}
{isWaitingForTransaction && }
diff --git a/packages/dev-frontend/src/components/Stability/NoDeposit.tsx b/packages/dev-frontend/src/components/Stability/NoDeposit.tsx
index 24ecc9f25..11e0db2b8 100644
--- a/packages/dev-frontend/src/components/Stability/NoDeposit.tsx
+++ b/packages/dev-frontend/src/components/Stability/NoDeposit.tsx
@@ -1,11 +1,41 @@
-import React, { useCallback } from "react";
-import { Card, Heading, Box, Flex, Button } from "theme-ui";
+import React, { useCallback, useState, useEffect } from "react";
+import { Card, Heading, Box, Flex, Button, Container, Close, Text } from "theme-ui";
import { InfoMessage } from "../InfoMessage";
import { useStabilityView } from "./context/StabilityViewContext";
import { RemainingLQTY } from "./RemainingLQTY";
import { Yield } from "./Yield";
+import { useLiquitySelector } from "@liquity/lib-react";
+import { useTransactionFunction } from "../Transaction";
+import { useLiquity } from "./../../hooks/LiquityContext";
+
+
+const selector = ( {bammAllowance}: any) => ({
+ bammAllowance
+});
+
+export const UnlockButton: React.FC = props => {
+ const { liquity } = useLiquity();
+ const [sendTransaction, transactionState] = useTransactionFunction(
+ "bamm-unlock",
+ liquity.send.bammUnlock.bind(liquity.send)
+ );
+
+ return (
+
+ {props.children}
+
+ )
+}
export const NoDeposit: React.FC = props => {
+ const { liquity } = useLiquity();
+ const { bammAllowance } = useLiquitySelector(selector);
const { dispatchEvent } = useStabilityView();
const handleOpenTrove = useCallback(() => {
@@ -22,14 +52,14 @@ export const NoDeposit: React.FC = props => {
- You can earn ETH and LQTY rewards by depositing LUSD.
+ You can earn LUSD and LQTY rewards by depositing LUSD.
- Deposit
+ Deposit
diff --git a/packages/dev-frontend/src/components/Stability/StabilityActionDescription.tsx b/packages/dev-frontend/src/components/Stability/StabilityActionDescription.tsx
index dda0f946d..000c54c32 100644
--- a/packages/dev-frontend/src/components/Stability/StabilityActionDescription.tsx
+++ b/packages/dev-frontend/src/components/Stability/StabilityActionDescription.tsx
@@ -1,6 +1,6 @@
import React from "react";
-import { Decimal, StabilityDeposit, StabilityDepositChange } from "@liquity/lib-base";
+import { Decimal, StabilityDeposit, StabilityDepositChange, Difference} from "@liquity/lib-base";
import { COIN, GT } from "../../strings";
import { ActionDescription, Amount } from "../ActionDescription";
@@ -8,15 +8,27 @@ import { ActionDescription, Amount } from "../ActionDescription";
type StabilityActionDescriptionProps = {
originalDeposit: StabilityDeposit;
change: StabilityDepositChange;
+ lusdDiff: Difference | undefined
+ ethDiff: Difference | undefined
};
export const StabilityActionDescription: React.FC = ({
originalDeposit,
- change
+ change,
+ lusdDiff,
+ ethDiff,
}) => {
const collateralGain = originalDeposit.collateralGain.nonZero?.prettify(4).concat(" ETH");
const lqtyReward = originalDeposit.lqtyReward.nonZero?.prettify().concat(" ", GT);
+ let eth = ethDiff?.prettify(4)
+ if(eth && (eth.indexOf("-") > -1 || eth.indexOf("+") > -1)){
+ eth = eth.substr(1)
+ }
+ let lusd = lusdDiff?.prettify(2)
+ if(lusd && (lusd.indexOf("-") > -1 || lusd.indexOf("+") > -1)){
+ lusd = lusd.substr(1)
+ }
return (
{change.depositLUSD ? (
@@ -31,22 +43,25 @@ export const StabilityActionDescription: React.FC
You are withdrawing{" "}
- {change.withdrawLUSD.prettify()} {COIN}
+ {lusd} LUSD
{" "}
+ {ethDiff?.absoluteValue?.gte(1/10000) &&
+ <>
+ And
+ {" "}
+
+ {eth} ETH
+ {" "}
+ >
+ }
to your wallet
>
)}
- {(collateralGain || lqtyReward) && (
+ {lqtyReward && (
<>
{" "}
and claiming at least{" "}
- {collateralGain && lqtyReward ? (
- <>
- {collateralGain} and {lqtyReward}
- >
- ) : (
- {collateralGain ?? lqtyReward}
- )}
+ {lqtyReward}
>
)}
.
diff --git a/packages/dev-frontend/src/components/Stability/StabilityDepositEditor.tsx b/packages/dev-frontend/src/components/Stability/StabilityDepositEditor.tsx
index 79fe659c2..f5e8d93ab 100644
--- a/packages/dev-frontend/src/components/Stability/StabilityDepositEditor.tsx
+++ b/packages/dev-frontend/src/components/Stability/StabilityDepositEditor.tsx
@@ -1,6 +1,13 @@
import React, { useState } from "react";
-import { Heading, Box, Card, Button } from "theme-ui";
+import { Heading, Box, Card, Button, Flex } from "theme-ui";
+import { ActionDescription } from "../ActionDescription";
+import {
+ selectForStabilityDepositChangeValidation,
+ validateStabilityDepositChange
+} from "./validation/validateStabilityDepositChange";
+
+import { useMyTransactionState } from "../Transaction";
import {
Decimal,
Decimalish,
@@ -18,48 +25,114 @@ import { EditableRow, StaticRow } from "../Trove/Editor";
import { LoadingOverlay } from "../LoadingOverlay";
import { InfoIcon } from "../InfoIcon";
-const select = ({ lusdBalance, lusdInStabilityPool }: LiquityStoreState) => ({
+const select = ({ lusdBalance, lusdInStabilityPool, stabilityDeposit }: LiquityStoreState) => ({
lusdBalance,
- lusdInStabilityPool
+ lusdInStabilityPool,
+ stabilityDeposit
});
type StabilityDepositEditorProps = {
originalDeposit: StabilityDeposit;
- editedLUSD: Decimal;
+ editedUSD: Decimal;
changePending: boolean;
dispatch: (action: { type: "setDeposit"; newValue: Decimalish } | { type: "revert" }) => void;
};
+const selectPrice = ({ price }: LiquityStoreState) => price;
+
export const StabilityDepositEditor: React.FC = ({
originalDeposit,
- editedLUSD,
+ editedUSD,
changePending,
dispatch,
children
}) => {
- const { lusdBalance, lusdInStabilityPool } = useLiquitySelector(select);
+ const { lusdBalance, lusdInStabilityPool, stabilityDeposit } = useLiquitySelector(select);
const editingState = useState();
+ const price = useLiquitySelector(selectPrice);
+ const validationContext = useLiquitySelector(selectForStabilityDepositChangeValidation);
- const edited = !editedLUSD.eq(originalDeposit.currentLUSD);
- const maxAmount = originalDeposit.currentLUSD.add(lusdBalance);
- const maxedOut = editedLUSD.eq(maxAmount);
+ const edited = !editedUSD.eq(stabilityDeposit.currentUSD);
- const lusdInStabilityPoolAfterChange = lusdInStabilityPool
- .sub(originalDeposit.currentLUSD)
- .add(editedLUSD);
+ const maxAmount = stabilityDeposit.currentUSD.add(lusdBalance);
+ const maxedOut = editedUSD.eq(maxAmount);
+ const ethInUsd = originalDeposit.currentUSD.sub(stabilityDeposit.currentLUSD)
+
const originalPoolShare = originalDeposit.currentLUSD.mulDiv(100, lusdInStabilityPool);
- const newPoolShare = editedLUSD.mulDiv(100, lusdInStabilityPoolAfterChange);
+
+ const {bammPoolShare, collateralGain} = stabilityDeposit;
+
+ const userTotalUsdInBamm = stabilityDeposit.currentUSD
+ const totalUsdInBamm = userTotalUsdInBamm.mulDiv(100, bammPoolShare);
+ const editedUserUsd = userTotalUsdInBamm.sub(stabilityDeposit.currentUSD).add(editedUSD);
+ const editedTotalUsdInBamm = totalUsdInBamm.sub(stabilityDeposit.currentUSD).add(editedUSD);
+ const editedBammPoolShare = editedUserUsd.mulDiv(100, editedTotalUsdInBamm)
+
+ /* USD balance
+ ====================================================================*/
+ const usdDiff = Difference.between(editedUSD, stabilityDeposit.currentUSD)
+
+ const bammPoolShareChange =
+ stabilityDeposit.currentUSD.nonZero &&
+ Difference.between(editedBammPoolShare, bammPoolShare).nonZero;
+
+ let newTotalLusd, newTotalEth;
+ if(bammPoolShareChange && !bammPoolShareChange?.nonZero || bammPoolShareChange?.positive){
+ newTotalLusd = stabilityDeposit.totalLusdInBamm.add(Decimal.from(usdDiff.absoluteValue||0));
+ newTotalEth = stabilityDeposit.totalEthInBamm;
+ } else {
+ newTotalLusd = stabilityDeposit.totalLusdInBamm.mul((editedTotalUsdInBamm.div(totalUsdInBamm)))
+ newTotalEth = stabilityDeposit.totalEthInBamm.mul((editedTotalUsdInBamm.div(totalUsdInBamm)))
+ }
+
+ const allowanceTxState = useMyTransactionState("bamm-unlock");
+ const waitingForTransaction =
+ allowanceTxState.type === "waitingForApproval" ||
+ allowanceTxState.type === "waitingForConfirmation";
+
+ /* ETH balance
+ ====================================================================*/
+ const newEthBalance = editedBammPoolShare.mul(newTotalEth).div(100)
+ const ethDiff = Difference.between(newEthBalance, stabilityDeposit.collateralGain).nonZero
+
+ /* LUSD balance
+ ====================================================================*/
+ const newLusdBalance = editedBammPoolShare.mul(newTotalLusd).div(100)
+ const lusdDiff = Difference.between(newLusdBalance, stabilityDeposit.currentLUSD).nonZeroish(15)
+
+ const [, description] = validateStabilityDepositChange(
+ originalDeposit,
+ editedUSD,
+ validationContext,
+ lusdDiff,
+ ethDiff,
+ );
+ const makingNewDeposit = originalDeposit.isEmpty;
+
+ /* pool share
+ ====================================================================*/
+ const lusdInStabilityPoolAfterChange = lusdInStabilityPool
+ .add(newTotalLusd)
+ .sub(stabilityDeposit.totalLusdInBamm);
+
+ const newPoolShare = (newTotalLusd.mulDiv(editedBammPoolShare, 100)).mulDiv(100, lusdInStabilityPoolAfterChange);
const poolShareChange =
originalDeposit.currentLUSD.nonZero &&
Difference.between(newPoolShare, originalPoolShare).nonZero;
+
+
+ const ethDiffInUsd = stabilityDeposit.currentUSD.sub(stabilityDeposit.currentLUSD)
+ const ethIsImportant = (ethDiffInUsd.div(stabilityDeposit.currentUSD)).gt(1/1000)
+ const showOverlay = changePending || waitingForTransaction
+ const showResetButton = edited && !showOverlay
return (
Stability Pool
- {edited && !changePending && (
+ {showResetButton && (
= ({
dispatch({ type: "setDeposit", newValue })}
/>
- {newPoolShare.infinite ? (
-
- ) : (
-
- )}
-
{!originalDeposit.isEmpty && (
<>
+
+
+ {ethIsImportant &&
+ Temporary ETH balance until rebalance takes place
+
+ }
+ />
+ }
/>
-
+ }
+
+ {newPoolShare.infinite ? (
+
+ ) : (
+
+ )}
+
+ {bammPoolShare.infinite ? (
+
+ ) : (
+
+ )}
+
= ({
/>
>
)}
+ {description ??
+ (makingNewDeposit ? (
+ Enter the amount of {COIN} you'd like to deposit.
+ ) : (
+ Adjust the {COIN} amount to deposit or withdraw.
+ ))}
{children}
-
- {changePending && }
+ {showOverlay && }
);
};
diff --git a/packages/dev-frontend/src/components/Stability/StabilityDepositManager.tsx b/packages/dev-frontend/src/components/Stability/StabilityDepositManager.tsx
index a7b8bd256..2f703aab1 100644
--- a/packages/dev-frontend/src/components/Stability/StabilityDepositManager.tsx
+++ b/packages/dev-frontend/src/components/Stability/StabilityDepositManager.tsx
@@ -19,7 +19,7 @@ import {
const init = ({ stabilityDeposit }: LiquityStoreState) => ({
originalDeposit: stabilityDeposit,
- editedLUSD: stabilityDeposit.currentLUSD,
+ editedUSD: stabilityDeposit.currentUSD,
changePending: false
});
@@ -40,10 +40,8 @@ const reduce = (
state: StabilityDepositManagerState,
action: StabilityDepositManagerAction
): StabilityDepositManagerState => {
- // console.log(state);
- // console.log(action);
- const { originalDeposit, editedLUSD, changePending } = state;
+ const { originalDeposit, editedUSD, changePending } = state;
switch (action.type) {
case "startChange": {
@@ -55,10 +53,10 @@ const reduce = (
return { ...state, changePending: false };
case "setDeposit":
- return { ...state, editedLUSD: Decimal.from(action.newValue) };
+ return { ...state, editedUSD: Decimal.from(action.newValue) };
case "revert":
- return { ...state, editedLUSD: originalDeposit.currentLUSD };
+ return { ...state, editedUSD: originalDeposit.currentUSD };
case "updateStore": {
const {
@@ -72,10 +70,15 @@ const reduce = (
const newState = { ...state, originalDeposit: updatedDeposit };
const changeCommitted =
- !updatedDeposit.initialLUSD.eq(originalDeposit.initialLUSD) ||
- updatedDeposit.currentLUSD.gt(originalDeposit.currentLUSD) ||
+ !updatedDeposit.bammPoolShare.eq(originalDeposit.bammPoolShare) ||
+ updatedDeposit.poolShare.gt(originalDeposit.poolShare) ||
+ updatedDeposit.currentUSD.lt(originalDeposit.currentUSD) ||
+ updatedDeposit.initialLUSD.lt(originalDeposit.initialLUSD) ||
+ updatedDeposit.currentLUSD.lt(originalDeposit.currentLUSD) ||
updatedDeposit.collateralGain.lt(originalDeposit.collateralGain) ||
- updatedDeposit.lqtyReward.lt(originalDeposit.lqtyReward);
+ updatedDeposit.lqtyReward.lt(originalDeposit.lqtyReward) ||
+ updatedDeposit.totalEthInBamm.lt(originalDeposit.totalEthInBamm) ||
+ updatedDeposit.totalLusdInBamm.lt(originalDeposit.totalLusdInBamm);
if (changePending && changeCommitted) {
return finishChange(revert(newState));
@@ -83,7 +86,7 @@ const reduce = (
return {
...newState,
- editedLUSD: updatedDeposit.apply(originalDeposit.whatChanged(editedLUSD))
+ editedUSD: updatedDeposit.apply(originalDeposit.whatChanged(editedUSD))
};
}
}
@@ -92,18 +95,19 @@ const reduce = (
const transactionId = "stability-deposit";
export const StabilityDepositManager: React.FC = () => {
- const [{ originalDeposit, editedLUSD, changePending }, dispatch] = useLiquityReducer(reduce, init);
+ const [{ originalDeposit, editedUSD, changePending }, dispatch] = useLiquityReducer(reduce, init);
const validationContext = useLiquitySelector(selectForStabilityDepositChangeValidation);
const { dispatchEvent } = useStabilityView();
const handleCancel = useCallback(() => {
dispatchEvent("CANCEL_PRESSED");
}, [dispatchEvent]);
-
- const [validChange, description] = validateStabilityDepositChange(
+ const [validChange] = validateStabilityDepositChange(
originalDeposit,
- editedLUSD,
- validationContext
+ editedUSD,
+ validationContext,
+ undefined,
+ undefined,
);
const makingNewDeposit = originalDeposit.isEmpty;
@@ -126,16 +130,11 @@ export const StabilityDepositManager: React.FC = () => {
return (
- {description ??
- (makingNewDeposit ? (
- Enter the amount of {COIN} you'd like to deposit.
- ) : (
- Adjust the {COIN} amount to deposit or withdraw.
- ))}
+
diff --git a/packages/dev-frontend/src/components/Stability/validation/validateStabilityDepositChange.tsx b/packages/dev-frontend/src/components/Stability/validation/validateStabilityDepositChange.tsx
index 5ce4ce1fa..3a7b9110d 100644
--- a/packages/dev-frontend/src/components/Stability/validation/validateStabilityDepositChange.tsx
+++ b/packages/dev-frontend/src/components/Stability/validation/validateStabilityDepositChange.tsx
@@ -5,21 +5,28 @@ import {
StabilityDepositChange
} from "@liquity/lib-base";
+import {
+ Difference
+} from "@liquity/lib-base";
+
import { COIN } from "../../../strings";
import { Amount } from "../../ActionDescription";
import { ErrorDescription } from "../../ErrorDescription";
import { StabilityActionDescription } from "../StabilityActionDescription";
+import { UnlockButton } from "../NoDeposit"
export const selectForStabilityDepositChangeValidation = ({
trove,
lusdBalance,
ownFrontend,
- haveUndercollateralizedTroves
-}: LiquityStoreState) => ({
+ haveUndercollateralizedTroves,
+ bammAllowance
+}: any) => ({
trove,
lusdBalance,
haveOwnFrontend: ownFrontend.status === "registered",
- haveUndercollateralizedTroves
+ haveUndercollateralizedTroves,
+ bammAllowance
});
type StabilityDepositChangeValidationContext = ReturnType<
@@ -32,8 +39,11 @@ export const validateStabilityDepositChange = (
{
lusdBalance,
haveOwnFrontend,
- haveUndercollateralizedTroves
- }: StabilityDepositChangeValidationContext
+ haveUndercollateralizedTroves,
+ bammAllowance
+ }: StabilityDepositChangeValidationContext,
+ lusdDiff: Difference| undefined,
+ ethDiff: Difference| undefined,
): [
validChange: StabilityDepositChange | undefined,
description: JSX.Element | undefined
@@ -66,6 +76,18 @@ export const validateStabilityDepositChange = (
];
}
+ if(change && !bammAllowance) {
+ return [
+ undefined,
+
+ You have no allowance. {" "}
+
+ click here to unlock.
+
+
+ ];
+ }
+
if (change.withdrawLUSD && haveUndercollateralizedTroves) {
return [
undefined,
@@ -76,5 +98,5 @@ export const validateStabilityDepositChange = (
];
}
- return [change, ];
+ return [change, ];
};
diff --git a/packages/dev-frontend/src/components/SystemStats.tsx b/packages/dev-frontend/src/components/SystemStats.tsx
index 9b554ad24..ec4cf471c 100644
--- a/packages/dev-frontend/src/components/SystemStats.tsx
+++ b/packages/dev-frontend/src/components/SystemStats.tsx
@@ -29,7 +29,7 @@ const Balances: React.FC = () => {
const GitHubCommit: React.FC<{ children?: string }> = ({ children }) =>
children?.match(/[0-9a-f]{40}/) ? (
- {children.substr(0, 7)}
+ {children.substr(0, 7)}
) : (
<>unknown>
);
@@ -47,7 +47,8 @@ const select = ({
borrowingRate,
redemptionRate,
totalStakedLQTY,
- frontend
+ frontend,
+ stabilityDeposit
}: LiquityStoreState) => ({
numberOfTroves,
price,
@@ -56,7 +57,8 @@ const select = ({
borrowingRate,
redemptionRate,
totalStakedLQTY,
- kickbackRate: frontend.status === "registered" ? frontend.kickbackRate : null
+ kickbackRate: frontend.status === "registered" ? frontend.kickbackRate : null,
+ stabilityDeposit
});
export const SystemStats: React.FC = ({ variant = "info", showBalances }) => {
@@ -73,11 +75,13 @@ export const SystemStats: React.FC = ({ variant = "info", show
total,
borrowingRate,
totalStakedLQTY,
- kickbackRate
+ kickbackRate,
+ stabilityDeposit
} = useLiquitySelector(select);
const lusdInStabilityPoolPct =
total.debt.nonZero && new Percent(lusdInStabilityPool.div(total.debt));
+ const lusdInBammPct = new Percent(stabilityDeposit.totalLusdInBamm.div(lusdInStabilityPool))
const totalCollateralRatioPct = new Percent(total.collateralRatio(price));
const borrowingFeePct = new Percent(borrowingRate);
const kickbackRatePct = frontendTag === AddressZero ? "100" : kickbackRate?.mul(100).prettify();
@@ -91,7 +95,7 @@ export const SystemStats: React.FC = ({ variant = "info", show
Protocol
-
+
= ({ variant = "info", show
{Decimal.from(numberOfTroves).prettify(0)}
+
{total.debt.shorten()}
-
+
+
{lusdInStabilityPoolPct && (
= ({ variant = "info", show
({lusdInStabilityPoolPct.toString(1)})
)}
+ {stabilityDeposit.totalLusdInBamm && (
+
+ {stabilityDeposit.totalLusdInBamm.shorten()}
+ ({lusdInBammPct.toString(1)})
+
+ )}
{totalStakedLQTY.shorten()}
+
= ({ variant = "info", show
>
{total.collateralRatioIsBelowCritical(price) ? Yes : "No"}
- {}
+
Frontend
diff --git a/packages/dev-frontend/src/components/WalletConnector.tsx b/packages/dev-frontend/src/components/WalletConnector.tsx
index 5e706f41a..924b20cea 100644
--- a/packages/dev-frontend/src/components/WalletConnector.tsx
+++ b/packages/dev-frontend/src/components/WalletConnector.tsx
@@ -11,6 +11,7 @@ import { ConnectionConfirmationDialog } from "./ConnectionConfirmationDialog";
import { MetaMaskIcon } from "./MetaMaskIcon";
import { Icon } from "./Icon";
import { Modal } from "./Modal";
+import { ConnectPage, device } from "./ConnectPage";
interface MaybeHasMetaMask {
ethereum?: {
@@ -86,6 +87,20 @@ type WalletConnectorProps = {
loader?: React.ReactNode;
};
+const inIframe = () => {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+}
+
+const mobileAndTabletCheck = ():boolean => {
+ let check = false;
+ (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
+ return check;
+};
+
export const WalletConnector: React.FC = ({ children, loader }) => {
const { activate, deactivate, active, error } = useWeb3React();
const triedAuthorizedConnection = useAuthorizedConnection();
@@ -115,27 +130,94 @@ export const WalletConnector: React.FC = ({ children, load
return <>{children}>;
}
+ const mobileAppLInk = inIframe() && mobileAndTabletCheck()
+ const onClick = ()=> {
+ dispatch({ type: "startActivating", connector: injectedConnector });
+ activate(injectedConnector);
+ }
+
return (
- <>
-
- {
+
+
+ {mobileAppLInk &&
+
+
+ CONNECT METAMASK
+
+ }
+ {!mobileAppLInk && {
dispatch({ type: "startActivating", connector: injectedConnector });
activate(injectedConnector);
}}
>
- {isMetaMask ? (
- <>
-
- Connect to MetaMask
- >
- ) : (
- <>
-
- Connect wallet
- >
- )}
-
+ CONNECT METAMASK
+ }
+
+
+ By using B.Protocol, you agree to the Terms and Conditions
+
+
{connectionState.type === "failed" && (
@@ -208,6 +290,6 @@ export const WalletConnector: React.FC = ({ children, load
)}
- >
+
);
};
diff --git a/packages/dev-frontend/src/index.css b/packages/dev-frontend/src/index.css
index 84c1d0a0e..913e3869c 100644
--- a/packages/dev-frontend/src/index.css
+++ b/packages/dev-frontend/src/index.css
@@ -1,5 +1,9 @@
-/* Can't style the html tag with theme-ui, so put styles in here */
+/* Can't style the html tag with theme-ui; so put styles in here */
html {
height: 100%;
}
+
+.hide {
+ display: none!important;
+}
diff --git a/packages/dev-frontend/src/pages/Dashboard.tsx b/packages/dev-frontend/src/pages/Dashboard.tsx
index aff2e34a4..742179a81 100644
--- a/packages/dev-frontend/src/pages/Dashboard.tsx
+++ b/packages/dev-frontend/src/pages/Dashboard.tsx
@@ -9,14 +9,18 @@ import { Staking } from "../components/Staking/Staking";
export const Dashboard: React.FC = () => (
-
+
+
+
-
+
);
diff --git a/packages/dev-frontend/src/theme.ts b/packages/dev-frontend/src/theme.ts
index 0bb8aa6be..e6ae85afa 100644
--- a/packages/dev-frontend/src/theme.ts
+++ b/packages/dev-frontend/src/theme.ts
@@ -462,6 +462,16 @@ const theme: Theme = {
"#root": {
height: "100%"
+ },
+ "html::-webkit-scrollbar": {
+ "display": "none"
+ },
+ "html": {
+ /* this will hide the scrollbar in mozilla based browsers */
+ "overflow": "-moz-scrollbars-none",
+ "scrollbar-width": "none",
+ /* this will hide the scrollbar in internet explorers */
+ "-ms-overflow-style": "none"
}
},
diff --git a/packages/lib-base/etc/lib-base.api.md b/packages/lib-base/etc/lib-base.api.md
index 57ed95e5d..15854ba81 100644
--- a/packages/lib-base/etc/lib-base.api.md
+++ b/packages/lib-base/etc/lib-base.api.md
@@ -4,6 +4,8 @@
```ts
+import { BigNumber } from '@ethersproject/bignumber';
+
// @internal (undocumented)
export class _CachedReadableLiquity implements _ReadableLiquityWithExtraParams {
constructor(readable: _ReadableLiquityWithExtraParams, cache: _LiquityReadCache);
@@ -102,6 +104,8 @@ export class Decimal {
// (undocumented)
static from(decimalish: Decimalish): Decimal;
// (undocumented)
+ static fromBigNumber(bigNumber: BigNumber): Decimal;
+ // (undocumented)
static fromBigNumberString(bigNumberString: string): Decimal;
// (undocumented)
gt(that: Decimalish): boolean;
@@ -138,6 +142,8 @@ export class Decimal {
// (undocumented)
prettify(precision?: number): string;
// (undocumented)
+ rawDivision(divider: Decimalish): Decimal;
+ // (undocumented)
shorten(): string;
// (undocumented)
sub(subtrahend: Decimalish): Decimal;
@@ -159,6 +165,8 @@ export class Difference {
// (undocumented)
static between(d1: Decimalish | undefined, d2: Decimalish | undefined): Difference;
// (undocumented)
+ div(divider: Decimalish): Difference;
+ // (undocumented)
get finite(): this | undefined;
// (undocumented)
get infinite(): this | undefined;
@@ -169,6 +177,8 @@ export class Difference {
// (undocumented)
get nonZero(): this | undefined;
// (undocumented)
+ nonZeroish(precision: number): this | undefined;
+ // (undocumented)
get positive(): this | undefined;
// (undocumented)
prettify(precision?: number): string;
@@ -591,19 +601,26 @@ export interface SentLiquityTransaction | undefined): Decimal;
+ readonly bammPoolShare: Decimal;
readonly collateralGain: Decimal;
readonly currentLUSD: Decimal;
+ readonly currentUSD: Decimal;
equals(that: StabilityDeposit): boolean;
readonly frontendTag: string;
readonly initialLUSD: Decimal;
// (undocumented)
get isEmpty(): boolean;
readonly lqtyReward: Decimal;
+ readonly poolShare: Decimal;
// @internal (undocumented)
toString(): string;
- whatChanged(thatLUSD: Decimalish): StabilityDepositChange | undefined;
+ // (undocumented)
+ readonly totalEthInBamm: Decimal;
+ // (undocumented)
+ readonly totalLusdInBamm: Decimal;
+ whatChanged(thatUSD: Decimalish): StabilityDepositChange | undefined;
}
// @public
diff --git a/packages/lib-base/src/Decimal.ts b/packages/lib-base/src/Decimal.ts
index 12d117398..83fbc9fa3 100644
--- a/packages/lib-base/src/Decimal.ts
+++ b/packages/lib-base/src/Decimal.ts
@@ -62,6 +62,10 @@ export class Decimal {
return new Decimal(BigNumber.from(bigNumberString));
}
+ static fromBigNumber(bigNumber: BigNumber): Decimal {
+ return new Decimal(bigNumber);
+ }
+
private static _fromString(representation: string): Decimal {
if (!representation || !representation.match(stringRepresentationFormat)) {
throw new Error(`bad decimal format: "${representation}"`);
@@ -210,6 +214,16 @@ export class Decimal {
return new Decimal(this._bigNumber.mul(DIGITS).div(divider._bigNumber));
}
+ rawDivision(divider: Decimalish): Decimal {
+ divider = Decimal.from(divider);
+
+ if (divider.isZero) {
+ return Decimal.INFINITY;
+ }
+
+ return new Decimal(this._bigNumber.div(divider._bigNumber));
+ }
+
/** @internal */
_divCeil(divider: Decimalish): Decimal {
divider = Decimal.from(divider);
@@ -386,10 +400,27 @@ export class Difference {
);
}
+ div(divider: Decimalish): Difference {
+ return new Difference(
+ this._number && {
+ sign: this._number.sign,
+ absoluteValue: this._number.absoluteValue.div(divider)
+ }
+ );
+ }
+
get nonZero(): this | undefined {
return this._number?.absoluteValue.nonZero && this;
}
+ nonZeroish(precision: number): this | undefined {
+ const zeroish = `0.${"0".repeat(precision)}5`;
+
+ if (this._number?.absoluteValue.gte(zeroish)) {
+ return this;
+ }
+ }
+
get positive(): this | undefined {
return this._number?.sign === "+" ? this : undefined;
}
diff --git a/packages/lib-base/src/StabilityDeposit.ts b/packages/lib-base/src/StabilityDeposit.ts
index 179772c6c..b9b7b4a8a 100644
--- a/packages/lib-base/src/StabilityDeposit.ts
+++ b/packages/lib-base/src/StabilityDeposit.ts
@@ -15,12 +15,21 @@ export type StabilityDepositChange =
* @public
*/
export class StabilityDeposit {
+ /** pool share of user in the BAMM wich has a share in the stability pool */
+ readonly bammPoolShare: Decimal;
+
+ /** pool share of user in the BAMM wich has a share in the stability pool */
+ readonly poolShare: Decimal;
+
/** Amount of LUSD in the Stability Deposit at the time of the last direct modification. */
readonly initialLUSD: Decimal;
/** Amount of LUSD left in the Stability Deposit. */
readonly currentLUSD: Decimal;
+ /** Amount of USD left in the Stability Deposit. */
+ readonly currentUSD: Decimal;
+
/** Amount of native currency (e.g. Ether) received in exchange for the used-up LUSD. */
readonly collateralGain: Decimal;
@@ -36,23 +45,33 @@ export class StabilityDeposit {
*/
readonly frontendTag: string;
+ readonly totalEthInBamm: Decimal;
+
+ readonly totalLusdInBamm: Decimal;
+
/** @internal */
constructor(
+ bammPoolShare: Decimal,
+ poolShare: Decimal,
initialLUSD: Decimal,
+ currentUSD: Decimal,
currentLUSD: Decimal,
collateralGain: Decimal,
lqtyReward: Decimal,
- frontendTag: string
+ frontendTag: string,
+ totalEthInBamm: Decimal,
+ totalLusdInBamm: Decimal
) {
+ this.bammPoolShare = bammPoolShare;
+ this.poolShare = poolShare;
this.initialLUSD = initialLUSD;
+ this.currentUSD = currentUSD;
this.currentLUSD = currentLUSD;
this.collateralGain = collateralGain;
this.lqtyReward = lqtyReward;
this.frontendTag = frontendTag;
-
- if (this.currentLUSD.gt(this.initialLUSD)) {
- throw new Error("currentLUSD can't be greater than initialLUSD");
- }
+ this.totalEthInBamm = totalEthInBamm;
+ this.totalLusdInBamm = totalLusdInBamm;
}
get isEmpty(): boolean {
@@ -67,10 +86,15 @@ export class StabilityDeposit {
/** @internal */
toString(): string {
return (
- `{ initialLUSD: ${this.initialLUSD}` +
+ `{ bammPoolShare: ${this.bammPoolShare}` +
+ `, poolShare: ${this.poolShare}` +
+ `, initialLUSD: ${this.initialLUSD}` +
`, currentLUSD: ${this.currentLUSD}` +
`, collateralGain: ${this.collateralGain}` +
`, lqtyReward: ${this.lqtyReward}` +
+ `, totalEthInBamm: ${this.totalEthInBamm}` +
+ `, lqtyReward: ${this.lqtyReward}` +
+ `, totalLusdInBamm: ${this.totalLusdInBamm}` +
`, frontendTag: "${this.frontendTag}" }`
);
}
@@ -79,12 +103,18 @@ export class StabilityDeposit {
* Compare to another instance of `StabilityDeposit`.
*/
equals(that: StabilityDeposit): boolean {
+
return (
+ this.bammPoolShare.eq(that.bammPoolShare) &&
+ this.poolShare.eq(that.poolShare) &&
+ this.currentUSD.eq(that.currentUSD) &&
this.initialLUSD.eq(that.initialLUSD) &&
this.currentLUSD.eq(that.currentLUSD) &&
this.collateralGain.eq(that.collateralGain) &&
this.lqtyReward.eq(that.lqtyReward) &&
- this.frontendTag === that.frontendTag
+ this.frontendTag === that.frontendTag &&
+ this.totalEthInBamm === that.totalEthInBamm &&
+ this.totalLusdInBamm === that.totalLusdInBamm
);
}
@@ -93,15 +123,15 @@ export class StabilityDeposit {
*
* @returns An object representing the change, or `undefined` if the deposited amounts are equal.
*/
- whatChanged(thatLUSD: Decimalish): StabilityDepositChange | undefined {
- thatLUSD = Decimal.from(thatLUSD);
+ whatChanged(thatUSD: Decimalish): StabilityDepositChange | undefined {
+ thatUSD = Decimal.from(thatUSD);
- if (thatLUSD.lt(this.currentLUSD)) {
- return { withdrawLUSD: this.currentLUSD.sub(thatLUSD), withdrawAllLUSD: thatLUSD.isZero };
+ if (thatUSD.lt(this.currentUSD)) {
+ return { withdrawLUSD: this.currentUSD.sub(thatUSD), withdrawAllLUSD: thatUSD.isZero };
}
- if (thatLUSD.gt(this.currentLUSD)) {
- return { depositLUSD: thatLUSD.sub(this.currentLUSD) };
+ if (thatUSD.gt(this.currentUSD)) {
+ return { depositLUSD: thatUSD.sub(this.currentUSD) };
}
}
@@ -112,15 +142,15 @@ export class StabilityDeposit {
*/
apply(change: StabilityDepositChange | undefined): Decimal {
if (!change) {
- return this.currentLUSD;
+ return this.currentUSD;
}
if (change.withdrawLUSD !== undefined) {
- return change.withdrawAllLUSD || this.currentLUSD.lte(change.withdrawLUSD)
+ return change.withdrawAllLUSD || this.currentUSD.lte(change.withdrawLUSD)
? Decimal.ZERO
- : this.currentLUSD.sub(change.withdrawLUSD);
+ : this.currentUSD.sub(change.withdrawLUSD);
} else {
- return this.currentLUSD.add(change.depositLUSD);
+ return this.currentUSD.add(change.depositLUSD);
}
}
}
diff --git a/packages/lib-ethers/abi/ActivePool.json b/packages/lib-ethers/abi/ActivePool.json
index f3022868c..762871169 100644
--- a/packages/lib-ethers/abi/ActivePool.json
+++ b/packages/lib-ethers/abi/ActivePool.json
@@ -330,6 +330,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManagerAddress",
diff --git a/packages/lib-ethers/abi/BAMM.json b/packages/lib-ethers/abi/BAMM.json
new file mode 100644
index 000000000..50a744256
--- /dev/null
+++ b/packages/lib-ethers/abi/BAMM.json
@@ -0,0 +1,1155 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_priceAggregator",
+ "type": "address"
+ },
+ {
+ "internalType": "address payable",
+ "name": "_SP",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_LUSD",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_LQTY",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_maxDiscount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address payable",
+ "name": "_feePool",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_fronEndTag",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "val",
+ "type": "uint256"
+ }
+ ],
+ "name": "Exit",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [],
+ "name": "Flee",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "val",
+ "type": "uint256"
+ }
+ ],
+ "name": "Join",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "previousOwner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "OwnershipTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "A",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "fee",
+ "type": "uint256"
+ }
+ ],
+ "name": "ParamsSet",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "lusdAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "ethAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "timestamp",
+ "type": "uint256"
+ }
+ ],
+ "name": "RebalanceSwap",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "src",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "dst",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "wad",
+ "type": "uint256"
+ }
+ ],
+ "name": "Tack",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "lusdAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "numShares",
+ "type": "uint256"
+ }
+ ],
+ "name": "UserDeposit",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "lusdAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "ethAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "numShares",
+ "type": "uint256"
+ }
+ ],
+ "name": "UserWithdraw",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "A",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "LUSD",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "MAX_A",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "MAX_FEE",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "MIN_A",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PRECISION",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "SP",
+ "outputs": [
+ {
+ "internalType": "contract StabilityPool",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "add",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "balance",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "bonus",
+ "outputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "crops",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "dec",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "lusdAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "deposit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "fee",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "feePool",
+ "outputs": [
+ {
+ "internalType": "address payable",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "fetchPrice",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "frontEndTag",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "gem",
+ "outputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "srcQty",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "getConversionRate",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "xQty",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "xBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "yBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "A",
+ "type": "uint256"
+ }
+ ],
+ "name": "getReturn",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "A",
+ "type": "uint256"
+ }
+ ],
+ "name": "getSumFixedPoint",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "lusdQty",
+ "type": "uint256"
+ }
+ ],
+ "name": "getSwapEthAmount",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "ethAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feeEthAmount",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "ilk",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "isOwner",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "maxDiscount",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "mul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "nav",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "nps",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "owner",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "priceAggregator",
+ "outputs": [
+ {
+ "internalType": "contract AggregatorV3Interface",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rdiv",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rmul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rmulup",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_A",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ }
+ ],
+ "name": "setParams",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "share",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "stake",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "stock",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "sub",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "lusdAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minEthReturn",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address payable",
+ "name": "dest",
+ "type": "address"
+ }
+ ],
+ "name": "swap",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "total",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "srcAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address payable",
+ "name": "destAddress",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "name": "trade",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "vat",
+ "outputs": [
+ {
+ "internalType": "contract VatLike",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wdiv",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wdivup",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "numShares",
+ "type": "uint256"
+ }
+ ],
+ "name": "withdraw",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wmul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
\ No newline at end of file
diff --git a/packages/lib-ethers/abi/BLens.json b/packages/lib-ethers/abi/BLens.json
new file mode 100644
index 000000000..72da28dc8
--- /dev/null
+++ b/packages/lib-ethers/abi/BLens.json
@@ -0,0 +1,247 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "add",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "internalType": "contract BAMM",
+ "name": "bamm",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "name": "getUnclaimedLqty",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "mul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rdiv",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rmul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "rmulup",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "sub",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wdiv",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wdivup",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "x",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "y",
+ "type": "uint256"
+ }
+ ],
+ "name": "wmul",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "z",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ }
+]
\ No newline at end of file
diff --git a/packages/lib-ethers/abi/BorrowerOperations.json b/packages/lib-ethers/abi/BorrowerOperations.json
index 79cef5c25..d0a08bed1 100644
--- a/packages/lib-ethers/abi/BorrowerOperations.json
+++ b/packages/lib-ethers/abi/BorrowerOperations.json
@@ -704,6 +704,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManager",
diff --git a/packages/lib-ethers/abi/CollSurplusPool.json b/packages/lib-ethers/abi/CollSurplusPool.json
index 079eebcce..8294e7028 100644
--- a/packages/lib-ethers/abi/CollSurplusPool.json
+++ b/packages/lib-ethers/abi/CollSurplusPool.json
@@ -246,6 +246,19 @@
"stateMutability": "nonpayable",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManagerAddress",
diff --git a/packages/lib-ethers/abi/CommunityIssuance.json b/packages/lib-ethers/abi/CommunityIssuance.json
index 77d7b6579..72a82c8a8 100644
--- a/packages/lib-ethers/abi/CommunityIssuance.json
+++ b/packages/lib-ethers/abi/CommunityIssuance.json
@@ -253,5 +253,18 @@
],
"stateMutability": "view",
"type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
}
]
\ No newline at end of file
diff --git a/packages/lib-ethers/abi/DefaultPool.json b/packages/lib-ethers/abi/DefaultPool.json
index 3b0b1eaff..8f9e8372c 100644
--- a/packages/lib-ethers/abi/DefaultPool.json
+++ b/packages/lib-ethers/abi/DefaultPool.json
@@ -276,6 +276,19 @@
"stateMutability": "nonpayable",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManagerAddress",
diff --git a/packages/lib-ethers/abi/HintHelpers.json b/packages/lib-ethers/abi/HintHelpers.json
index 65939a316..c346fe33e 100644
--- a/packages/lib-ethers/abi/HintHelpers.json
+++ b/packages/lib-ethers/abi/HintHelpers.json
@@ -414,6 +414,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManager",
diff --git a/packages/lib-ethers/abi/LQTYStaking.json b/packages/lib-ethers/abi/LQTYStaking.json
index a2d7f8f55..929675b0f 100644
--- a/packages/lib-ethers/abi/LQTYStaking.json
+++ b/packages/lib-ethers/abi/LQTYStaking.json
@@ -506,6 +506,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManagerAddress",
diff --git a/packages/lib-ethers/abi/LockupContractFactory.json b/packages/lib-ethers/abi/LockupContractFactory.json
index 00ddfa8fb..91ef546be 100644
--- a/packages/lib-ethers/abi/LockupContractFactory.json
+++ b/packages/lib-ethers/abi/LockupContractFactory.json
@@ -195,5 +195,18 @@
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
}
]
\ No newline at end of file
diff --git a/packages/lib-ethers/abi/PriceFeed.json b/packages/lib-ethers/abi/PriceFeed.json
index 06c8a1d45..2318f4634 100644
--- a/packages/lib-ethers/abi/PriceFeed.json
+++ b/packages/lib-ethers/abi/PriceFeed.json
@@ -256,5 +256,18 @@
],
"stateMutability": "view",
"type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
}
]
\ No newline at end of file
diff --git a/packages/lib-ethers/abi/SortedTroves.json b/packages/lib-ethers/abi/SortedTroves.json
index f8fcbccc9..2146a3818 100644
--- a/packages/lib-ethers/abi/SortedTroves.json
+++ b/packages/lib-ethers/abi/SortedTroves.json
@@ -430,6 +430,19 @@
"stateMutability": "nonpayable",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManager",
diff --git a/packages/lib-ethers/abi/StabilityPool.json b/packages/lib-ethers/abi/StabilityPool.json
index b2a055efb..da402f988 100644
--- a/packages/lib-ethers/abi/StabilityPool.json
+++ b/packages/lib-ethers/abi/StabilityPool.json
@@ -1214,6 +1214,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "troveManager",
diff --git a/packages/lib-ethers/abi/TroveManager.json b/packages/lib-ethers/abi/TroveManager.json
index 31c09aa9d..3a6c96b1f 100644
--- a/packages/lib-ethers/abi/TroveManager.json
+++ b/packages/lib-ethers/abi/TroveManager.json
@@ -1682,6 +1682,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [
{
diff --git a/packages/lib-ethers/abi/Unipool.json b/packages/lib-ethers/abi/Unipool.json
index d5bebb266..a6861b499 100644
--- a/packages/lib-ethers/abi/Unipool.json
+++ b/packages/lib-ethers/abi/Unipool.json
@@ -370,6 +370,19 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
{
"inputs": [],
"name": "uniToken",
diff --git a/packages/lib-ethers/deployments/default/goerli.json b/packages/lib-ethers/deployments/default/goerli.json
index c55eb16a1..b45c066dd 100644
--- a/packages/lib-ethers/deployments/default/goerli.json
+++ b/packages/lib-ethers/deployments/default/goerli.json
@@ -22,6 +22,8 @@
"priceFeed": "0x7620B306164bc5d5491b99b8ACd515a06b15f29B",
"sortedTroves": "0xBf7022bae995A8690A14e59284c48F7dF6b9F6F2",
"stabilityPool": "0xC3809338e11f64B6a0b04c06AB3aC180bD781520",
+ "bamm": "0xaB875C981e1ee054b4C8A6F17DE8461a0dB55b80",
+ "bLens": "0x4E870f95bB5659c45233fBF3d3A1a01a38F84c5a",
"gasPool": "0x9440CD7990b122099A8eF59F74668206C660eED5",
"unipool": "0xf195b1B4E8F74C91a4D3013626752eB108D7Ed9B",
"lusdToken": "0xfBf329Bd38D57e0db8777915d4835341b97052A9",
diff --git a/packages/lib-ethers/deployments/default/kovan.json b/packages/lib-ethers/deployments/default/kovan.json
index 903df9714..32f85f11c 100644
--- a/packages/lib-ethers/deployments/default/kovan.json
+++ b/packages/lib-ethers/deployments/default/kovan.json
@@ -22,6 +22,8 @@
"priceFeed": "0xbA49275F8F890E7296F64b3e81F1Ada656030150",
"sortedTroves": "0x91656701b33eca6425A239930FccAA842D0E2031",
"stabilityPool": "0x04d630Bff6dea193Fd644dEcfC460db249854a02",
+ "bamm": "0x111CA91E821013F2Ab60652adeFB5115b9521CBF",
+ "bLens": "0x4E870f95bB5659c45233fBF3d3A1a01a38F84c5a",
"gasPool": "0xd97E194C0659F0d7b051EdF3E317BF4F7A675770",
"unipool": "0xeed1782CaD8bad6eD2072C85e4c5821e67cCf1E1",
"lusdToken": "0x0b02b94638daa719290b5214825dA625af08A02F",
diff --git a/packages/lib-ethers/deployments/default/mainnet.json b/packages/lib-ethers/deployments/default/mainnet.json
index 8cc61c129..035cc8fe6 100644
--- a/packages/lib-ethers/deployments/default/mainnet.json
+++ b/packages/lib-ethers/deployments/default/mainnet.json
@@ -22,6 +22,8 @@
"priceFeed": "0x4c517D4e2C851CA76d7eC94B805269Df0f2201De",
"sortedTroves": "0x8FdD3fbFEb32b28fb73555518f8b361bCeA741A6",
"stabilityPool": "0x66017D22b0f8556afDd19FC67041899Eb65a21bb",
+ "bamm": "0x0d3AbAA7E088C2c82f54B2f47613DA438ea8C598",
+ "bLens": "0x9DCc156dfDc09bb52c7489e6CE5c1A9C90572064",
"gasPool": "0x9555b042F969E561855e5F28cB1230819149A8d9",
"unipool": "0xd37a77E71ddF3373a79BE2eBB76B6c4808bDF0d5",
"lusdToken": "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0",
diff --git a/packages/lib-ethers/deployments/default/rinkeby.json b/packages/lib-ethers/deployments/default/rinkeby.json
index d62dff24c..a0ee1a600 100644
--- a/packages/lib-ethers/deployments/default/rinkeby.json
+++ b/packages/lib-ethers/deployments/default/rinkeby.json
@@ -22,6 +22,8 @@
"priceFeed": "0x692Cd2D9Df7EFD91Ccd17F9eEFD72BAEE0584DB9",
"sortedTroves": "0xac064890c6343F67450ba7aB97df6De38A8D7da8",
"stabilityPool": "0xB8eb11f9eFF55378dfB692296C32DF020f5CC7fF",
+ "bamm": "0xaB875C981e1ee054b4C8A6F17DE8461a0dB55b80",
+ "bLens": "0x4E870f95bB5659c45233fBF3d3A1a01a38F84c5a",
"gasPool": "0xbbb26b40c1B32ba1F342DAbC65234516dd29BB44",
"unipool": "0x866b252Acff4c45d6978032865aFb9923D11992A",
"lusdToken": "0x9C5AE6852622ddE455B6Fca4C1551FC0352531a3",
diff --git a/packages/lib-ethers/deployments/default/ropsten.json b/packages/lib-ethers/deployments/default/ropsten.json
index 648d60126..7cef94ea4 100644
--- a/packages/lib-ethers/deployments/default/ropsten.json
+++ b/packages/lib-ethers/deployments/default/ropsten.json
@@ -22,6 +22,8 @@
"priceFeed": "0x7151f3828948B8824C677DA8daf7BD13014c1187",
"sortedTroves": "0xA401f217DB7d84432C98272F637E9d450c6D16f2",
"stabilityPool": "0x02dD2a33d0bBF40343CD09941F275978b1cd4ab9",
+ "bamm": "0xaB875C981e1ee054b4C8A6F17DE8461a0dB55b80",
+ "bLens": "0x4E870f95bB5659c45233fBF3d3A1a01a38F84c5a",
"gasPool": "0x8c2706b7bF86576F16Db7C099F5a62E7Ce8F0661",
"unipool": "0x8C2C33247A691a98d05B60c2D7448687b6C56a86",
"lusdToken": "0x99Fda92878c1d2f1e0971D1937C50CC578A33E3D",
diff --git a/packages/lib-ethers/etc/lib-ethers.api.md b/packages/lib-ethers/etc/lib-ethers.api.md
index a4a313dec..ad395e68f 100644
--- a/packages/lib-ethers/etc/lib-ethers.api.md
+++ b/packages/lib-ethers/etc/lib-ethers.api.md
@@ -60,6 +60,8 @@ export class BlockPolledLiquityStore extends LiquityStore;
+ // (undocumented)
+ getBammAllowance(overrides?: EthersCallOverrides): Promise;
// @internal (undocumented)
_getBlockTimestamp(blockTag?: BlockTag): Promise;
// (undocumented)
@@ -183,6 +187,8 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity
getUniTokenAllowance(address?: string, overrides?: EthersCallOverrides): Promise;
// (undocumented)
getUniTokenBalance(address?: string, overrides?: EthersCallOverrides): Promise;
+ // (undocumented)
+ getWitdrawsSpShare(withdrawAmount: Decimalish): Promise;
hasStore(): this is EthersLiquityWithStore;
hasStore(store: "blockPolled"): this is EthersLiquityWithStore;
// (undocumented)
@@ -335,6 +341,8 @@ export class PopulatableEthersLiquity implements PopulatableLiquity>;
// (undocumented)
+ bammUnlock(overrides?: EthersTransactionOverrides): Promise;
+ // (undocumented)
borrowLUSD(amount: Decimalish, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>;
// (undocumented)
claimCollateralSurplus(overrides?: EthersTransactionOverrides): Promise>;
@@ -460,6 +468,8 @@ export class ReadableEthersLiquity implements ReadableLiquity {
static _from(connection: EthersLiquityConnection): ReadableEthersLiquity;
// @internal (undocumented)
_getActivePool(overrides?: EthersCallOverrides): Promise;
+ // (undocumented)
+ getBammAllowance(overrides?: EthersCallOverrides): Promise;
// @internal (undocumented)
_getBlockTimestamp(blockTag?: BlockTag): Promise;
// (undocumented)
@@ -518,6 +528,8 @@ export class ReadableEthersLiquity implements ReadableLiquity {
getUniTokenAllowance(address?: string, overrides?: EthersCallOverrides): Promise;
// (undocumented)
getUniTokenBalance(address?: string, overrides?: EthersCallOverrides): Promise;
+ // (undocumented)
+ getWitdrawsSpShare(withdrawAmount: Decimalish): Promise;
hasStore(): this is ReadableEthersLiquityWithStore;
hasStore(store: "blockPolled"): this is ReadableEthersLiquityWithStore;
}
@@ -538,6 +550,8 @@ export class SendableEthersLiquity implements SendableLiquity>;
// (undocumented)
+ bammUnlock(overrides?: EthersTransactionOverrides): Promise;
+ // (undocumented)
borrowLUSD(amount: Decimalish, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>;
// (undocumented)
claimCollateralSurplus(overrides?: EthersTransactionOverrides): Promise>;
diff --git a/packages/lib-ethers/scripts/generate-types.ts b/packages/lib-ethers/scripts/generate-types.ts
index a51570ab3..e2a98dfb2 100644
--- a/packages/lib-ethers/scripts/generate-types.ts
+++ b/packages/lib-ethers/scripts/generate-types.ts
@@ -21,6 +21,8 @@ import PriceFeed from "../../contracts/artifacts/contracts/PriceFeed.sol/PriceFe
import PriceFeedTestnet from "../../contracts/artifacts/contracts/TestContracts/PriceFeedTestnet.sol/PriceFeedTestnet.json";
import SortedTroves from "../../contracts/artifacts/contracts/SortedTroves.sol/SortedTroves.json";
import StabilityPool from "../../contracts/artifacts/contracts/StabilityPool.sol/StabilityPool.json";
+import BAMM from "../../contracts/artifacts/contracts/B.Protocol/BAMM.sol/BAMM.json";
+import BLens from "../../contracts/artifacts/contracts/B.Protocol/BLens.sol/BLens.json";
import TroveManager from "../../contracts/artifacts/contracts/TroveManager.sol/TroveManager.json";
import Unipool from "../../contracts/artifacts/contracts/LPRewards/Unipool.sol/Unipool.json";
@@ -161,6 +163,8 @@ const contractArtifacts = [
PriceFeedTestnet,
SortedTroves,
StabilityPool,
+ BAMM,
+ BLens,
TroveManager,
Unipool
];
diff --git a/packages/lib-ethers/src/BlockPolledLiquityStore.ts b/packages/lib-ethers/src/BlockPolledLiquityStore.ts
index 33aa03cd3..9344d7bf7 100644
--- a/packages/lib-ethers/src/BlockPolledLiquityStore.ts
+++ b/packages/lib-ethers/src/BlockPolledLiquityStore.ts
@@ -38,6 +38,8 @@ export interface BlockPolledLiquityStoreExtraState {
/** @internal */
_feesFactory: (blockTimestamp: number, recoveryMode: boolean) => Fees;
+
+ bammAllowance: boolean;
}
/**
@@ -92,6 +94,7 @@ export class BlockPolledLiquityStore extends LiquityStore {
+ return this._readable.getWitdrawsSpShare(withdrawAmount);
+ }
+
/** {@inheritDoc @liquity/lib-base#ReadableLiquity.getRemainingStabilityPoolLQTYReward} */
getRemainingStabilityPoolLQTYReward(overrides?: EthersCallOverrides): Promise {
return this._readable.getRemainingStabilityPoolLQTYReward(overrides);
@@ -307,6 +312,10 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity
return this._readable.getTotalStakedLQTY(overrides);
}
+ getBammAllowance(overrides?: EthersCallOverrides): Promise {
+ return this._readable.getBammAllowance(overrides);
+ }
+
/** {@inheritDoc @liquity/lib-base#ReadableLiquity.getFrontendStatus} */
getFrontendStatus(address?: string, overrides?: EthersCallOverrides): Promise {
return this._readable.getFrontendStatus(address, overrides);
diff --git a/packages/lib-ethers/src/EthersLiquityConnection.ts b/packages/lib-ethers/src/EthersLiquityConnection.ts
index d6cf89cf0..ed25d99d7 100644
--- a/packages/lib-ethers/src/EthersLiquityConnection.ts
+++ b/packages/lib-ethers/src/EthersLiquityConnection.ts
@@ -28,10 +28,11 @@ const deployments: {
[chainId: number]: _LiquityDeploymentJSON | undefined;
} = {
[mainnet.chainId]: mainnet,
- [ropsten.chainId]: ropsten,
- [rinkeby.chainId]: rinkeby,
- [goerli.chainId]: goerli,
+ //[ropsten.chainId]: ropsten,
+ //[rinkeby.chainId]: rinkeby,
+ //[goerli.chainId]: goerli,
[kovan.chainId]: kovan,
+ [1337]: mainnet,
...(dev !== null ? { [dev.chainId]: dev } : {})
};
diff --git a/packages/lib-ethers/src/PopulatableEthersLiquity.ts b/packages/lib-ethers/src/PopulatableEthersLiquity.ts
index 65570f059..ad0c7440d 100644
--- a/packages/lib-ethers/src/PopulatableEthersLiquity.ts
+++ b/packages/lib-ethers/src/PopulatableEthersLiquity.ts
@@ -84,6 +84,8 @@ const addGasForLQTYIssuance = (gas: BigNumber) => gas.add(50000);
const addGasForUnipoolRewardUpdate = (gas: BigNumber) => gas.add(20000);
+const simpleAddGas = (gas: BigNumber) => gas.add(1000);
+
// To get the best entropy available, we'd do something like:
//
// const bigRandomNumber = () =>
@@ -1070,22 +1072,41 @@ export class PopulatableEthersLiquity
);
}
+ // /** {@inheritDoc @liquity/lib-base#PopulatableLiquity.depositLUSDInStabilityPool} */
+ // async depositLUSDInStabilityPool(
+ // amount: Decimalish,
+ // frontendTag?: string,
+ // overrides?: EthersTransactionOverrides
+ // ): Promise> {
+ // const { stabilityPool } = _getContracts(this._readable.connection);
+ // const depositLUSD = Decimal.from(amount);
+
+ // return this._wrapStabilityDepositTopup(
+ // { depositLUSD },
+ // await stabilityPool.estimateAndPopulate.provideToSP(
+ // { ...overrides },
+ // addGasForLQTYIssuance,
+ // depositLUSD.hex,
+ // frontendTag ?? this._readable.connection.frontendTag ?? AddressZero
+ // )
+ // );
+ // }
/** {@inheritDoc @liquity/lib-base#PopulatableLiquity.depositLUSDInStabilityPool} */
async depositLUSDInStabilityPool(
amount: Decimalish,
frontendTag?: string,
overrides?: EthersTransactionOverrides
): Promise> {
- const { stabilityPool } = _getContracts(this._readable.connection);
+ const { bamm } = _getContracts(this._readable.connection);
const depositLUSD = Decimal.from(amount);
-
+
return this._wrapStabilityDepositTopup(
{ depositLUSD },
- await stabilityPool.estimateAndPopulate.provideToSP(
+ await bamm.estimateAndPopulate.deposit(
{ ...overrides },
addGasForLQTYIssuance,
- depositLUSD.hex,
- frontendTag ?? this._readable.connection.frontendTag ?? AddressZero
+ depositLUSD.hex
+ // frontendTag ?? this._readable.connection.frontendTag ?? AddressZero
)
);
}
@@ -1095,13 +1116,46 @@ export class PopulatableEthersLiquity
amount: Decimalish,
overrides?: EthersTransactionOverrides
): Promise> {
- const { stabilityPool } = _getContracts(this._readable.connection);
-
+ const { bamm } = _getContracts(this._readable.connection);
+ const spShareToWithdraw = await this._readable.getWitdrawsSpShare(amount)
return this._wrapStabilityDepositWithdrawal(
- await stabilityPool.estimateAndPopulate.withdrawFromSP(
+ await bamm.estimateAndPopulate.withdraw(
{ ...overrides },
addGasForLQTYIssuance,
- Decimal.from(amount).hex
+ Decimal.from(spShareToWithdraw).hex
+ )
+ );
+ }
+
+ // /** {@inheritDoc @liquity/lib-base#PopulatableLiquity.withdrawLUSDFromStabilityPool} */
+ // async withdrawLUSDFromStabilityPool(
+ // amount: Decimalish,
+ // overrides?: EthersTransactionOverrides
+ // ): Promise> {
+ // const { stabilityPool } = _getContracts(this._readable.connection);
+
+ // return this._wrapStabilityDepositWithdrawal(
+ // await stabilityPool.estimateAndPopulate.withdrawFromSP(
+ // { ...overrides },
+ // addGasForLQTYIssuance,
+ // Decimal.from(amount).hex
+ // )
+ // );
+ // }
+
+ /** {@inheritDoc @liquity/lib-base#PopulatableLiquity.bammUnlock} */
+ async bammUnlock(
+ overrides?: EthersTransactionOverrides
+ ): Promise {
+ const { bamm, lusdToken } = _getContracts(this._readable.connection);
+ const maxAllowance = BigNumber.from("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+
+ return this._wrapSimpleTransaction(
+ await lusdToken.estimateAndPopulate.approve(
+ { ...overrides },
+ simpleAddGas,
+ bamm.address,
+ maxAllowance
)
);
}
@@ -1110,10 +1164,10 @@ export class PopulatableEthersLiquity
async withdrawGainsFromStabilityPool(
overrides?: EthersTransactionOverrides
): Promise> {
- const { stabilityPool } = _getContracts(this._readable.connection);
+ const { bamm } = _getContracts(this._readable.connection);
return this._wrapStabilityPoolGainsWithdrawal(
- await stabilityPool.estimateAndPopulate.withdrawFromSP(
+ await bamm.estimateAndPopulate.withdraw(
{ ...overrides },
addGasForLQTYIssuance,
Decimal.ZERO.hex
diff --git a/packages/lib-ethers/src/ReadableEthersLiquity.ts b/packages/lib-ethers/src/ReadableEthersLiquity.ts
index 4d1a927f2..3707fb6a4 100644
--- a/packages/lib-ethers/src/ReadableEthersLiquity.ts
+++ b/packages/lib-ethers/src/ReadableEthersLiquity.ts
@@ -1,7 +1,9 @@
import { BlockTag } from "@ethersproject/abstract-provider";
+import { BigNumber } from "@ethersproject/bignumber";
import {
Decimal,
+ Decimalish,
Fees,
FrontendStatus,
LiquityStore,
@@ -247,35 +249,136 @@ export class ReadableEthersLiquity implements ReadableLiquity {
return activePool.add(defaultPool);
}
+ async getWitdrawsSpShare(
+ withdrawAmount: Decimalish, // todo should be Decimalish
+ ): Promise {
+ const address = _requireAddress(this.connection);
+ const { stabilityPool, bamm } = _getContracts(this.connection);
+
+ console.log(withdrawAmount)
+
+ const [
+ stake, // users share in the bamm
+ {currentUSD}
+ ] = await Promise.all([
+ bamm.stake(address),
+ this.getStabilityDeposit(address)
+ ]);
+ // amount * stake / currentUSD
+ const spShare = decimalify(stake).mul(Decimal.from(withdrawAmount)).div(currentUSD).toString()
+
+ return spShare
+ }
+
/** {@inheritDoc @liquity/lib-base#ReadableLiquity.getStabilityDeposit} */
async getStabilityDeposit(
address?: string,
overrides?: EthersCallOverrides
): Promise {
+ const _1e18 = BigNumber.from(10).pow(18)
+
address ??= _requireAddress(this.connection);
- const { stabilityPool } = _getContracts(this.connection);
+ const { stabilityPool, bamm, lqtyToken, priceFeed, bLens } = _getContracts(this.connection);
+ const bammLqtyBalancePromise = lqtyToken.balanceOf(bamm.address, { ...overrides})
const [
{ frontEndTag, initialValue },
- currentLUSD,
- collateralGain,
- lqtyReward
+ currentBammLUSD,
+ bammPendingEth,
+ bammPendingLqtyReward,
+ total,
+ stake,
+ totalLusdInSp,
+ unclaimedLqty,
+ share,
+ stock,
] = await Promise.all([
stabilityPool.deposits(address, { ...overrides }),
- stabilityPool.getCompoundedLUSDDeposit(address, { ...overrides }),
- stabilityPool.getDepositorETHGain(address, { ...overrides }),
- stabilityPool.getDepositorLQTYGain(address, { ...overrides })
+ stabilityPool.getCompoundedLUSDDeposit(bamm.address, { ...overrides }),
+ stabilityPool.getDepositorETHGain(bamm.address, { ...overrides }),
+ stabilityPool.getDepositorLQTYGain(bamm.address, { ...overrides }),
+ bamm.total({ ...overrides }),
+ bamm.stake(address, { ...overrides}),
+ stabilityPool.getTotalLUSDDeposits({ ...overrides }),
+ bLens.callStatic.getUnclaimedLqty(address, bamm.address, lqtyToken.address),
+ bamm.share({ ...overrides }),
+ bamm.stock({ ...overrides}),
]);
+ const bammLqtyBalance = await bammLqtyBalancePromise
+ // stake times lusd divided by total
+ const currentLUSD = stake.mul(currentBammLUSD).div(total)
+ // stabilityDeposit.currentLUSD.mulDiv(100, lusdInStabilityPool);
+ const bammShare = Decimal.fromBigNumber(currentBammLUSD).mul(100).div(Decimal.fromBigNumber(totalLusdInSp))
+ // bamm share in SP times stake div by total
+ const poolShare = bammShare.mul(Decimal.fromBigNumber(stake)).div(Decimal.fromBigNumber(total))
+
+ const bammEthBalance = (await bamm.provider.getBalance(bamm.address)).add(bammPendingEth)
+ const currentETH = stake.mul(bammEthBalance).div(total)
+
+ const price = await priceFeed.callStatic.fetchPrice({ ...overrides })
+
+ const currentUSD = currentLUSD.add(currentETH.mul(price).div(_1e18))
+
+ const bammPoolShare = Decimal.fromBigNumber(stake).mulDiv(100, Decimal.fromBigNumber(total))
+ // balance + pending - stock
+ if(total.gt(BigNumber.from(0))){
+ console.log(
+ JSON.stringify({
+ bammPendingEth: bammPendingEth.toString(),
+ bammLqtyBalance: bammLqtyBalance.toString(),
+ bammPendingLqtyReward: bammPendingLqtyReward.toString(),
+ stock: stock.toString(),
+ share: share.toString(),
+ total: total.toString(),
+ stake: stake.toString(),
+ }, null, 2)
+ )
+ }
+
return new StabilityDeposit(
+ bammPoolShare,
+ poolShare,
decimalify(initialValue),
- decimalify(currentLUSD),
- decimalify(collateralGain),
- decimalify(lqtyReward),
- frontEndTag
+ Decimal.fromBigNumber(currentUSD),
+ Decimal.fromBigNumber(currentLUSD),
+ Decimal.fromBigNumber(currentETH),
+ Decimal.fromBigNumber(unclaimedLqty),
+ frontEndTag,
+ Decimal.fromBigNumber(bammEthBalance),
+ Decimal.fromBigNumber(currentBammLUSD)
);
}
+ // /** {@inheritDoc @liquity/lib-base#ReadableLiquity.getStabilityDeposit} */
+ // async getStabilityDeposit(
+ // address?: string,
+ // overrides?: EthersCallOverrides
+ // ): Promise {
+ // address ??= _requireAddress(this.connection);
+ // const { stabilityPool } = _getContracts(this.connection);
+
+ // const [
+ // { frontEndTag, initialValue },
+ // currentLUSD,
+ // collateralGain,
+ // lqtyReward
+ // ] = await Promise.all([
+ // stabilityPool.deposits(address, { ...overrides }),
+ // stabilityPool.getCompoundedLUSDDeposit(address, { ...overrides }),
+ // stabilityPool.getDepositorETHGain(address, { ...overrides }),
+ // stabilityPool.getDepositorLQTYGain(address, { ...overrides })
+ // ]);
+
+ // return new StabilityDeposit(
+ // decimalify(initialValue),
+ // decimalify(currentLUSD),
+ // decimalify(collateralGain),
+ // decimalify(lqtyReward),
+ // frontEndTag
+ // );
+ // }
+
/** {@inheritDoc @liquity/lib-base#ReadableLiquity.getRemainingStabilityPoolLQTYReward} */
async getRemainingStabilityPoolLQTYReward(overrides?: EthersCallOverrides): Promise {
const { communityIssuance } = _getContracts(this.connection);
@@ -506,6 +609,17 @@ export class ReadableEthersLiquity implements ReadableLiquity {
? { status: "registered", kickbackRate: decimalify(kickbackRate) }
: { status: "unregistered" };
}
+
+ async getBammAllowance(overrides?: EthersCallOverrides): Promise {
+ const { lusdToken, bamm } = _getContracts(this.connection);
+ const address = _requireAddress(this.connection);
+ const reallyLargeAllowance = BigNumber.from("0x8888888888888888888888888888888888888888888888888888888888888888")
+
+ const allowance = await lusdToken.allowance(address, bamm.address)
+ console.log({allowance})
+ const bammAllowance = allowance.gt(reallyLargeAllowance)
+ return bammAllowance;
+ }
}
type Resolved = T extends Promise ? U : T;
@@ -611,6 +725,10 @@ class _BlockPolledReadableEthersLiquity
return this._blockHit(overrides) ? this.store.state.total : this._readable.getTotal(overrides);
}
+ async getWitdrawsSpShare(withdrawAmount: Decimalish): Promise {
+ return this._readable.getWitdrawsSpShare(withdrawAmount)
+ }
+
async getStabilityDeposit(
address?: string,
overrides?: EthersCallOverrides
@@ -756,4 +874,10 @@ class _BlockPolledReadableEthersLiquity
_getRemainingLiquidityMiningLQTYRewardCalculator(): Promise<(blockTimestamp: number) => Decimal> {
throw new Error("Method not implemented.");
}
+
+ async getBammAllowance(overrides?: EthersCallOverrides): Promise {
+ return this._blockHit(overrides)
+ ? this.store.state.bammAllowance
+ : this._readable.getBammAllowance(overrides);
+ }
}
diff --git a/packages/lib-ethers/src/SendableEthersLiquity.ts b/packages/lib-ethers/src/SendableEthersLiquity.ts
index 225632632..689b8fc3f 100644
--- a/packages/lib-ethers/src/SendableEthersLiquity.ts
+++ b/packages/lib-ethers/src/SendableEthersLiquity.ts
@@ -155,6 +155,13 @@ export class SendableEthersLiquity
return this._populate.withdrawGainsFromStabilityPool(overrides).then(sendTransaction);
}
+ /** {@inheritDoc @liquity/lib-base#SendableLiquity.bammUnlock} */
+ bammUnlock(
+ overrides?: EthersTransactionOverrides
+ ): Promise {
+ return this._populate.bammUnlock(overrides).then(sendTransaction);
+ }
+
/** {@inheritDoc @liquity/lib-base#SendableLiquity.transferCollateralGainToTrove} */
transferCollateralGainToTrove(
overrides?: EthersTransactionOverrides
diff --git a/packages/lib-ethers/src/contracts.ts b/packages/lib-ethers/src/contracts.ts
index 25a23685b..85670510d 100644
--- a/packages/lib-ethers/src/contracts.ts
+++ b/packages/lib-ethers/src/contracts.ts
@@ -28,6 +28,8 @@ import priceFeedAbi from "../abi/PriceFeed.json";
import priceFeedTestnetAbi from "../abi/PriceFeedTestnet.json";
import sortedTrovesAbi from "../abi/SortedTroves.json";
import stabilityPoolAbi from "../abi/StabilityPool.json";
+import BAMMAbi from "../abi/BAMM.json";
+import BLensAbi from "../abi/BLens.json";
import gasPoolAbi from "../abi/GasPool.json";
import unipoolAbi from "../abi/Unipool.json";
import iERC20Abi from "../abi/IERC20.json";
@@ -50,6 +52,8 @@ import {
PriceFeedTestnet,
SortedTroves,
StabilityPool,
+ BAMM,
+ BLens,
GasPool,
Unipool,
ERC20Mock,
@@ -180,6 +184,8 @@ export interface _LiquityContracts {
priceFeed: PriceFeed | PriceFeedTestnet;
sortedTroves: SortedTroves;
stabilityPool: StabilityPool;
+ bamm: BAMM;
+ bLens: BLens;
gasPool: GasPool;
unipool: Unipool;
uniToken: IERC20 | ERC20Mock;
@@ -216,6 +222,8 @@ const getAbi = (priceFeedIsTestnet: boolean, uniTokenIsMock: boolean): LiquityCo
priceFeed: priceFeedIsTestnet ? priceFeedTestnetAbi : priceFeedAbi,
sortedTroves: sortedTrovesAbi,
stabilityPool: stabilityPoolAbi,
+ bamm: BAMMAbi,
+ bLens: BLensAbi,
gasPool: gasPoolAbi,
collSurplusPool: collSurplusPoolAbi,
unipool: unipoolAbi,
diff --git a/packages/lib-ethers/types/index.ts b/packages/lib-ethers/types/index.ts
index bb85ff4c4..4b9613150 100644
--- a/packages/lib-ethers/types/index.ts
+++ b/packages/lib-ethers/types/index.ts
@@ -28,6 +28,7 @@ interface ActivePoolTransactions {
increaseLUSDDebt(_amount: BigNumberish, _overrides?: Overrides): Promise;
sendETH(_account: string, _amount: BigNumberish, _overrides?: Overrides): Promise;
setAddresses(_borrowerOperationsAddress: string, _troveManagerAddress: string, _stabilityPoolAddress: string, _defaultPoolAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface ActivePool
@@ -92,6 +93,7 @@ interface BorrowerOperationsTransactions {
openTrove(_maxFeePercentage: BigNumberish, _LUSDAmount: BigNumberish, _upperHint: string, _lowerHint: string, _overrides?: PayableOverrides): Promise;
repayLUSD(_LUSDAmount: BigNumberish, _upperHint: string, _lowerHint: string, _overrides?: Overrides): Promise;
setAddresses(_troveManagerAddress: string, _activePoolAddress: string, _defaultPoolAddress: string, _stabilityPoolAddress: string, _gasPoolAddress: string, _collSurplusPoolAddress: string, _priceFeedAddress: string, _sortedTrovesAddress: string, _lusdTokenAddress: string, _lqtyStakingAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
withdrawColl(_collWithdrawal: BigNumberish, _upperHint: string, _lowerHint: string, _overrides?: Overrides): Promise;
withdrawLUSD(_maxFeePercentage: BigNumberish, _LUSDAmount: BigNumberish, _upperHint: string, _lowerHint: string, _overrides?: Overrides): Promise;
}
@@ -145,6 +147,7 @@ interface CollSurplusPoolTransactions {
accountSurplus(_account: string, _amount: BigNumberish, _overrides?: Overrides): Promise;
claimColl(_account: string, _overrides?: Overrides): Promise;
setAddresses(_borrowerOperationsAddress: string, _troveManagerAddress: string, _activePoolAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface CollSurplusPool
@@ -183,6 +186,7 @@ interface CommunityIssuanceTransactions {
issueLQTY(_overrides?: Overrides): Promise;
sendLQTY(_account: string, _LQTYamount: BigNumberish, _overrides?: Overrides): Promise;
setAddresses(_lqtyTokenAddress: string, _stabilityPoolAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface CommunityIssuance
@@ -214,6 +218,7 @@ interface DefaultPoolTransactions {
increaseLUSDDebt(_amount: BigNumberish, _overrides?: Overrides): Promise;
sendETHToActivePool(_amount: BigNumberish, _overrides?: Overrides): Promise;
setAddresses(_troveManagerAddress: string, _activePoolAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface DefaultPool
@@ -312,6 +317,7 @@ interface HintHelpersCalls {
interface HintHelpersTransactions {
setAddresses(_sortedTrovesAddress: string, _troveManagerAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface HintHelpers
@@ -361,6 +367,7 @@ interface LockupContractFactoryCalls {
interface LockupContractFactoryTransactions {
deployLockupContract(_beneficiary: string, _unlockTime: BigNumberish, _overrides?: Overrides): Promise;
setLQTYTokenAddress(_lqtyTokenAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface LockupContractFactory
@@ -446,6 +453,7 @@ interface LQTYStakingTransactions {
increaseF_LUSD(_LUSDFee: BigNumberish, _overrides?: Overrides): Promise;
setAddresses(_lqtyTokenAddress: string, _lusdTokenAddress: string, _troveManagerAddress: string, _borrowerOperationsAddress: string, _activePoolAddress: string, _overrides?: Overrides): Promise;
stake(_LQTYamount: BigNumberish, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
unstake(_LQTYamount: BigNumberish, _overrides?: Overrides): Promise;
}
@@ -562,6 +570,7 @@ interface PriceFeedCalls {
interface PriceFeedTransactions {
fetchPrice(_overrides?: Overrides): Promise;
setAddresses(_priceAggregatorAddress: string, _tellorCallerAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface PriceFeed
@@ -618,6 +627,7 @@ interface SortedTrovesTransactions {
reInsert(_id: string, _newNICR: BigNumberish, _prevId: string, _nextId: string, _overrides?: Overrides): Promise;
remove(_id: string, _overrides?: Overrides): Promise;
setParams(_size: BigNumberish, _troveManagerAddress: string, _borrowerOperationsAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
}
export interface SortedTroves
@@ -688,6 +698,7 @@ interface StabilityPoolTransactions {
provideToSP(_amount: BigNumberish, _frontEndTag: string, _overrides?: Overrides): Promise;
registerFrontEnd(_kickbackRate: BigNumberish, _overrides?: Overrides): Promise;
setAddresses(_borrowerOperationsAddress: string, _troveManagerAddress: string, _activePoolAddress: string, _lusdTokenAddress: string, _sortedTrovesAddress: string, _priceFeedAddress: string, _communityIssuanceAddress: string, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
withdrawETHGainToTrove(_upperHint: string, _lowerHint: string, _overrides?: Overrides): Promise;
withdrawFromSP(_amount: BigNumberish, _overrides?: Overrides): Promise;
}
@@ -750,6 +761,111 @@ export interface StabilityPool
extractEvents(logs: Log[], name: "UserDepositChanged"): _TypedLogDescription<{ _depositor: string; _newDeposit: BigNumber }>[];
}
+interface BAMMCalls {
+ A(_overrides?: CallOverrides): Promise;
+ LUSD(_overrides?: CallOverrides): Promise;
+ MAX_A(_overrides?: CallOverrides): Promise;
+ MAX_FEE(_overrides?: CallOverrides): Promise;
+ MIN_A(_overrides?: CallOverrides): Promise;
+ PRECISION(_overrides?: CallOverrides): Promise;
+ SP(_overrides?: CallOverrides): Promise;
+ add(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ balanceOf(owner: string, _overrides?: CallOverrides): Promise;
+ bonus(_overrides?: CallOverrides): Promise;
+ crops(arg0: string, _overrides?: CallOverrides): Promise;
+ dec(_overrides?: CallOverrides): Promise;
+ decimals(_overrides?: CallOverrides): Promise;
+ fee(_overrides?: CallOverrides): Promise;
+ feePool(_overrides?: CallOverrides): Promise;
+ fetchPrice(_overrides?: CallOverrides): Promise;
+ frontEndTag(_overrides?: CallOverrides): Promise;
+ gem(_overrides?: CallOverrides): Promise;
+ getConversionRate(arg0: string, arg1: string, srcQty: BigNumberish, arg3: BigNumberish, _overrides?: CallOverrides): Promise;
+ getReturn(xQty: BigNumberish, xBalance: BigNumberish, yBalance: BigNumberish, A: BigNumberish, _overrides?: CallOverrides): Promise;
+ getSumFixedPoint(x: BigNumberish, y: BigNumberish, A: BigNumberish, _overrides?: CallOverrides): Promise;
+ getSwapEthAmount(lusdQty: BigNumberish, _overrides?: CallOverrides): Promise<{ ethAmount: BigNumber; feeEthAmount: BigNumber }>;
+ ilk(_overrides?: CallOverrides): Promise;
+ isOwner(_overrides?: CallOverrides): Promise;
+ maxDiscount(_overrides?: CallOverrides): Promise;
+ mul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ name(_overrides?: CallOverrides): Promise;
+ owner(_overrides?: CallOverrides): Promise;
+ priceAggregator(_overrides?: CallOverrides): Promise;
+ rdiv(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ rmul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ rmulup(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ share(_overrides?: CallOverrides): Promise;
+ stake(arg0: string, _overrides?: CallOverrides): Promise;
+ stock(_overrides?: CallOverrides): Promise;
+ sub(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ symbol(_overrides?: CallOverrides): Promise;
+ total(_overrides?: CallOverrides): Promise;
+ totalSupply(_overrides?: CallOverrides): Promise;
+ vat(_overrides?: CallOverrides): Promise;
+ wdiv(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ wdivup(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ wmul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+}
+
+interface BAMMTransactions {
+ deposit(lusdAmount: BigNumberish, _overrides?: Overrides): Promise;
+ nav(_overrides?: Overrides): Promise;
+ nps(_overrides?: Overrides): Promise;
+ setParams(_A: BigNumberish, _fee: BigNumberish, _overrides?: Overrides): Promise;
+ swap(lusdAmount: BigNumberish, minEthReturn: BigNumberish, dest: string, _overrides?: Overrides): Promise;
+ trade(arg0: string, srcAmount: BigNumberish, arg2: string, destAddress: string, arg4: BigNumberish, arg5: boolean, _overrides?: PayableOverrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
+ withdraw(numShares: BigNumberish, _overrides?: Overrides): Promise;
+}
+
+export interface BAMM
+ extends _TypedLiquityContract {
+ readonly filters: {
+ Exit(val?: null): EventFilter;
+ Flee(): EventFilter;
+ Join(val?: null): EventFilter;
+ OwnershipTransferred(previousOwner?: string | null, newOwner?: string | null): EventFilter;
+ ParamsSet(A?: null, fee?: null): EventFilter;
+ RebalanceSwap(user?: string | null, lusdAmount?: null, ethAmount?: null, timestamp?: null): EventFilter;
+ Tack(src?: string | null, dst?: string | null, wad?: null): EventFilter;
+ Transfer(_from?: string | null, _to?: string | null, _value?: null): EventFilter;
+ UserDeposit(user?: string | null, lusdAmount?: null, numShares?: null): EventFilter;
+ UserWithdraw(user?: string | null, lusdAmount?: null, ethAmount?: null, numShares?: null): EventFilter;
+ };
+ extractEvents(logs: Log[], name: "Exit"): _TypedLogDescription<{ val: BigNumber }>[];
+ extractEvents(logs: Log[], name: "Flee"): _TypedLogDescription<{ }>[];
+ extractEvents(logs: Log[], name: "Join"): _TypedLogDescription<{ val: BigNumber }>[];
+ extractEvents(logs: Log[], name: "OwnershipTransferred"): _TypedLogDescription<{ previousOwner: string; newOwner: string }>[];
+ extractEvents(logs: Log[], name: "ParamsSet"): _TypedLogDescription<{ A: BigNumber; fee: BigNumber }>[];
+ extractEvents(logs: Log[], name: "RebalanceSwap"): _TypedLogDescription<{ user: string; lusdAmount: BigNumber; ethAmount: BigNumber; timestamp: BigNumber }>[];
+ extractEvents(logs: Log[], name: "Tack"): _TypedLogDescription<{ src: string; dst: string; wad: BigNumber }>[];
+ extractEvents(logs: Log[], name: "Transfer"): _TypedLogDescription<{ _from: string; _to: string; _value: BigNumber }>[];
+ extractEvents(logs: Log[], name: "UserDeposit"): _TypedLogDescription<{ user: string; lusdAmount: BigNumber; numShares: BigNumber }>[];
+ extractEvents(logs: Log[], name: "UserWithdraw"): _TypedLogDescription<{ user: string; lusdAmount: BigNumber; ethAmount: BigNumber; numShares: BigNumber }>[];
+}
+
+interface BLensCalls {
+ add(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ mul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ rdiv(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ rmul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ rmulup(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ sub(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ wdiv(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ wdivup(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+ wmul(x: BigNumberish, y: BigNumberish, _overrides?: CallOverrides): Promise;
+}
+
+interface BLensTransactions {
+ getUnclaimedLqty(user: string, bamm: string, token: string, _overrides?: Overrides): Promise;
+}
+
+export interface BLens
+ extends _TypedLiquityContract {
+ readonly filters: {
+ };
+}
+
interface TroveManagerCalls {
BETA(_overrides?: CallOverrides): Promise;
BOOTSTRAP_PERIOD(_overrides?: CallOverrides): Promise;
@@ -830,6 +946,7 @@ interface TroveManagerTransactions {
removeStake(_borrower: string, _overrides?: Overrides): Promise;
setAddresses(_borrowerOperationsAddress: string, _activePoolAddress: string, _defaultPoolAddress: string, _stabilityPoolAddress: string, _gasPoolAddress: string, _collSurplusPoolAddress: string, _priceFeedAddress: string, _lusdTokenAddress: string, _sortedTrovesAddress: string, _lqtyTokenAddress: string, _lqtyStakingAddress: string, _overrides?: Overrides): Promise;
setTroveStatus(_borrower: string, _num: BigNumberish, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
updateStakeAndTotalStakes(_borrower: string, _overrides?: Overrides): Promise;
updateTroveRewardSnapshots(_borrower: string, _overrides?: Overrides): Promise;
}
@@ -910,6 +1027,7 @@ interface UnipoolTransactions {
claimReward(_overrides?: Overrides): Promise;
setParams(_lqtyTokenAddress: string, _uniTokenAddress: string, _duration: BigNumberish, _overrides?: Overrides): Promise;
stake(amount: BigNumberish, _overrides?: Overrides): Promise;
+ transferOwnership(newOwner: string, _overrides?: Overrides): Promise;
withdraw(amount: BigNumberish, _overrides?: Overrides): Promise;
withdrawAndClaim(_overrides?: Overrides): Promise;
}
diff --git a/packages/lib-ethers/utils/deploy.ts b/packages/lib-ethers/utils/deploy.ts
index 74d0708c6..2523b77e1 100644
--- a/packages/lib-ethers/utils/deploy.ts
+++ b/packages/lib-ethers/utils/deploy.ts
@@ -170,6 +170,7 @@ const connectContracts = async (
priceFeed,
sortedTroves,
stabilityPool,
+ bamm,
gasPool,
unipool,
uniToken
diff --git a/papers/B.Protocol/FPS_B.AMM_Liquity_Assessment_FINAL.pdf b/papers/B.Protocol/FPS_B.AMM_Liquity_Assessment_FINAL.pdf
new file mode 100644
index 000000000..45ef69dcd
Binary files /dev/null and b/papers/B.Protocol/FPS_B.AMM_Liquity_Assessment_FINAL.pdf differ
diff --git a/yarn.lock b/yarn.lock
index 37099c3ab..0c89f156e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1238,6 +1238,13 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@cloudflare/kv-asset-handler@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.1.3.tgz#3eaaf962b16c48a7189db5d5ac1c4dca4e2ed1a0"
+ integrity sha512-FNcunDuTmEfQTLRLtA6zz+buIXUHj1soPvSWzzQFBC+n2lsy+CGf/NIrR3SEPCmsVNQj70/Jx2lViCpq+09YpQ==
+ dependencies:
+ mime "^2.5.2"
+
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -14182,7 +14189,7 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-mime@^2.4.3, mime@^2.4.4:
+mime@^2.4.3, mime@^2.4.4, mime@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==