diff --git a/contracts/impls/margin/MarginFlowProtocol.sol b/contracts/impls/margin/MarginFlowProtocol.sol index a220423..0eeea3e 100644 --- a/contracts/impls/margin/MarginFlowProtocol.sol +++ b/contracts/impls/margin/MarginFlowProtocol.sol @@ -125,12 +125,14 @@ contract MarginFlowProtocol is FlowProtocolBase { mapping (MarginLiquidityPoolInterface => mapping(address => bool)) public traderHasPaidFees; mapping (MarginLiquidityPoolInterface => mapping(address => bool)) public traderIsMarginCalled; mapping(address => mapping (address => bool)) public tradingPairWhitelist; + mapping (address => mapping(address => mapping (bool => Percentage.Percent))) public currentSwapRates; - Percentage.Percent public currentSwapRate; uint256 public minLeverage; uint256 public maxLeverage; uint256 public minLeverageAmount; - uint256 public rateUnit; + uint256 public swapRateUnit; + bool constant public LONG = true; + bool constant public SHORT = false; uint256 constant public TRADER_MARGIN_CALL_FEE = 20 ether; // TODO uint256 constant public TRADER_LIQUIDATION_FEE = 60 ether; // TODO @@ -150,51 +152,62 @@ contract MarginFlowProtocol is FlowProtocolBase { * @dev Initialize the MarginFlowProtocol. * @param _oracle The price oracle * @param _moneyMarket The money market. + * @param _safetyProtocol The _safetyProtocol. * @param _liquidityPoolRegistry The liquidity pool registry. - * @param _initialSwapRate The initial swap rate as percentage. + * @param _initialMinLeverage The _initialMinLeverage. + * @param _initialMaxLeverage The _initialMaxLeverage. + * @param _initialMinLeverageAmount The _initialMinLeverageAmount. + * @param _swapRateUnit The _swapRateUnit. */ function initialize( PriceOracleInterface _oracle, MoneyMarketInterface _moneyMarket, MarginFlowProtocolSafety _safetyProtocol, MarginLiquidityPoolRegistry _liquidityPoolRegistry, - uint256 _initialSwapRate, uint256 _initialMinLeverage, uint256 _initialMaxLeverage, uint256 _initialMinLeverageAmount, - uint256 _rateUnit + uint256 _swapRateUnit ) external initializer { FlowProtocolBase.initialize(_oracle, _moneyMarket); safetyProtocol = _safetyProtocol; liquidityPoolRegistry = _liquidityPoolRegistry; - currentSwapRate = Percentage.Percent(_initialSwapRate); minLeverage = _initialMinLeverage; maxLeverage = _initialMaxLeverage; minLeverageAmount = _initialMinLeverageAmount; - rateUnit = _rateUnit; + swapRateUnit = _swapRateUnit; } /** * @dev Add new trading pair, only for the owner. * @param _base The base token. * @param _quote The quote token. + * @param _swapRateLong The swap rate as percentage for longs. + * @param _swapRateShort The swap rate as percentage for shorts. */ - function addTradingPair(address _base, address _quote) external onlyOwner { + function addTradingPair(address _base, address _quote, uint256 _swapRateLong, uint256 _swapRateShort) external onlyOwner { require(_base != address(0) && _quote != address(0), "0"); require(_base != _quote, "TP3"); require(!tradingPairWhitelist[_base][_quote], "TP2"); + + currentSwapRates[_base][_quote][LONG] = Percentage.Percent(_swapRateLong); + currentSwapRates[_base][_quote][SHORT] = Percentage.Percent(_swapRateShort); tradingPairWhitelist[_base][_quote] = true; emit NewTradingPair(_base, _quote); } /** - * @dev Set new swap rate, only for the owner. - * @param _newSwapRate The new swap rate as percentage. + * @dev Set new swap rate for token pair, only for the owner. + * @param _base The base token. + * @param _quote The quote token. + * @param _newSwapRateLong The new swap rate as percentage for longs. + * @param _newSwapRateShort The new swap rate as percentage for shorts. */ - function setCurrentSwapRate(uint256 _newSwapRate) external onlyOwner { - require(_newSwapRate > 0, "0"); - currentSwapRate = Percentage.Percent(_newSwapRate); + function setCurrentSwapRateForPair(address _base, address _quote, uint256 _newSwapRateLong, uint256 _newSwapRateShort) external onlyOwner { + require(_newSwapRateLong > 0 && _newSwapRateShort > 0, "0"); + currentSwapRates[_base][_quote][LONG] = Percentage.Percent(_newSwapRateLong); + currentSwapRates[_base][_quote][SHORT] = Percentage.Percent(_newSwapRateShort); } /** @@ -427,7 +440,7 @@ contract MarginFlowProtocol is FlowProtocolBase { Position memory position = positionsById[_positionId]; uint256 timeDeltaInSeconds = now.sub(position.timeWhenOpened); - uint256 daysSinceOpen = timeDeltaInSeconds.div(rateUnit); + uint256 daysSinceOpen = timeDeltaInSeconds.div(swapRateUnit); uint256 leveragedDebitsAbs = position.leveragedDebitsInUsd >= 0 ? uint256(position.leveragedDebitsInUsd) : uint256(-position.leveragedDebitsInUsd); @@ -578,7 +591,7 @@ contract MarginFlowProtocol is FlowProtocolBase { int256(leveragedDebits).mul(debitSignum), int256(leveragedHeldInUsd).mul(debitSignum), marginHeld, - currentSwapRate, + currentSwapRates[_pair.base][_pair.quote][_leverage > 0 ? LONG : SHORT], now ); diff --git a/cucumber/step-definitions/margin.steps.ts b/cucumber/step-definitions/margin.steps.ts index 62ca464..5ab6f4d 100644 --- a/cucumber/step-definitions/margin.steps.ts +++ b/cucumber/step-definitions/margin.steps.ts @@ -96,8 +96,9 @@ const parseAmount = (amount: string): BN => { }; const parseSwapRate = (amount: string): BN => { - const parsed = amount.replace('%', ''); + const parsed = amount.replace(/%|-/g, ''); const onePercentSpread = new BN(web3.utils.toWei('1')).div(new BN(100)); + return onePercentSpread.mul(new BN(parsed)); }; @@ -257,7 +258,7 @@ Given('oracle price', async (table: TableDefinition) => { Given('margin spread', async (table: TableDefinition) => { for (const [pair, value] of table.rows()) { - const spreadValue = parseAmount(value); // TODO? .div(new BN(10000)); + const spreadValue = parseAmount(value); const { baseAddress, quoteAddress } = parseTradingPair(pair); await sendTx({ @@ -297,13 +298,16 @@ Given( Given('margin set swap rate', async (table: TableDefinition) => { for (const [pair, long, short] of table.rows()) { - const { baseAddress, quoteAddress } = parseTradingPair(pair); // TODO + const { baseAddress, quoteAddress } = parseTradingPair(pair); const longSwapRate = parseSwapRate(long); - const shortSpread = parseSwapRate(short); // TODO + const shortSpread = parseSwapRate(short); await sendTx({ - contractMethod: flowMarginProtocolContract.methods.setCurrentSwapRate( + contractMethod: flowMarginProtocolContract.methods.setCurrentSwapRateForPair( + baseAddress, + quoteAddress, longSwapRate, + shortSpread, ), to: flowMarginProtocolAddress, }); @@ -322,6 +326,8 @@ Given(/margin enable trading pair (\D*)/, async (tradingPair: string) => { contractMethod: flowMarginProtocolContract.methods.addTradingPair( baseAddress, quoteAddress, + 1, + 1, ), to: flowMarginProtocolAddress, }); diff --git a/migrations/deploy_contracts.ts b/migrations/deploy_contracts.ts index 7021854..74e6b7a 100644 --- a/migrations/deploy_contracts.ts +++ b/migrations/deploy_contracts.ts @@ -270,7 +270,6 @@ module.exports = (artifacts: Truffle.Artifacts, web3: Web3) => { moneyMarket.address, marginProtocolSafety.address, marginLiquidityPoolRegistry.address, - initialSwapRate, 1, 50, 2, @@ -298,14 +297,54 @@ module.exports = (artifacts: Truffle.Artifacts, web3: Web3) => { const usd = await moneyMarket.baseToken(); - await marginProtocol.addTradingPair(fEUR.address, usd); - await marginProtocol.addTradingPair(usd, fEUR.address); - await marginProtocol.addTradingPair(fJPY.address, usd); - await marginProtocol.addTradingPair(usd, fJPY.address); - await marginProtocol.addTradingPair(fXAU.address, usd); - await marginProtocol.addTradingPair(usd, fXAU.address); - await marginProtocol.addTradingPair(fAAPL.address, usd); - await marginProtocol.addTradingPair(usd, fAAPL.address); + await marginProtocol.addTradingPair( + fEUR.address, + usd, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + usd, + fEUR.address, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + fJPY.address, + usd, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + usd, + fJPY.address, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + fXAU.address, + usd, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + usd, + fXAU.address, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + fAAPL.address, + usd, + initialSwapRate, + initialSwapRate, + ); + await marginProtocol.addTradingPair( + usd, + fAAPL.address, + initialSwapRate, + initialSwapRate, + ); // approve default account diff --git a/test/margin/marginFlowProtocol.ts b/test/margin/marginFlowProtocol.ts index 9299ce9..5df7443 100644 --- a/test/margin/marginFlowProtocol.ts +++ b/test/margin/marginFlowProtocol.ts @@ -66,7 +66,8 @@ contract('MarginFlowProtocol', accounts => { let initialEurPrice: BN; let initialJpyPrice: BN; - let initialSwapRate: BN; + let initialSwapRateLong: BN; + let initialSwapRateShort: BN; beforeEach(async () => { const oracleImpl = await SimplePriceOracle.new(); @@ -84,7 +85,8 @@ contract('MarginFlowProtocol', accounts => { initialUsdPrice = fromPercent(100); initialEurPrice = fromPercent(120); initialJpyPrice = fromPercent(200); - initialSwapRate = fromPercent(2); + initialSwapRateLong = fromPercent(2); + initialSwapRateShort = fromPercent(2); usd = await createTestToken( [liquidityProvider, dollar(50000)], @@ -124,7 +126,6 @@ contract('MarginFlowProtocol', accounts => { moneyMarket.address, protocolSafety.address, liquidityPoolRegistry.address, - initialSwapRate, 1, 50, 2, @@ -189,8 +190,18 @@ contract('MarginFlowProtocol', accounts => { from: liquidityProvider, }); await liquidityPoolRegistry.verifyPool(liquidityPool.address); - await protocol.addTradingPair(jpy, eur); - await protocol.addTradingPair(usd.address, eur); + await protocol.addTradingPair( + jpy, + eur, + initialSwapRateLong, + initialSwapRateShort, + ); + await protocol.addTradingPair( + usd.address, + eur, + initialSwapRateLong, + initialSwapRateShort, + ); await oracle.feedPrice(usd.address, initialUsdPrice, { from: owner, @@ -236,62 +247,128 @@ contract('MarginFlowProtocol', accounts => { describe('when adding a trading pair', () => { it('sets new parameter', async () => { - await protocol.addTradingPair(eur, jpy); + await protocol.addTradingPair( + eur, + jpy, + initialSwapRateLong, + initialSwapRateShort, + ); expect(await protocol.tradingPairWhitelist(eur, jpy)).to.be.true; }); it('reverts when trading pair already whitelisted', async () => { - await protocol.addTradingPair(eur, jpy); + await protocol.addTradingPair( + eur, + jpy, + initialSwapRateLong, + initialSwapRateShort, + ); await expectRevert( - protocol.addTradingPair(eur, jpy), + protocol.addTradingPair( + eur, + jpy, + initialSwapRateLong, + initialSwapRateShort, + ), messages.tradingPairAlreadyWhitelisted, ); }); it('reverts when trading pair tokens are identical', async () => { await expectRevert( - protocol.addTradingPair(eur, eur), + protocol.addTradingPair( + eur, + eur, + initialSwapRateLong, + initialSwapRateShort, + ), messages.tradingPairTokensMustBeDifferent, ); }); it('allows only owner to add a trading pair', async () => { await expectRevert( - protocol.addTradingPair(eur, jpy, { from: alice }), + protocol.addTradingPair( + eur, + jpy, + initialSwapRateLong, + initialSwapRateShort, + { + from: alice, + }, + ), messages.onlyOwner, ); }); }); - describe('when setting new parameters', () => { - describe(`when using setCurrentSwapRate`, () => { - let newSwapRate: BN; + describe('when using setCurrentSwapRate', () => { + const LONG = true; + const SHORT = false; + let newSwapRateLong: BN; + let newSwapRateShort: BN; - beforeEach(() => { - newSwapRate = bn(123); - }); + beforeEach(() => { + newSwapRateLong = bn(123); + newSwapRateShort = bn(456); + }); - it('sets new currentSwapRate', async () => { - await protocol.setCurrentSwapRate(newSwapRate); - const newStoredSwapRate = await protocol.currentSwapRate(); - expect(newStoredSwapRate).to.be.bignumber.equals(newSwapRate); - }); + it('sets new currentSwapRate', async () => { + await protocol.setCurrentSwapRateForPair( + usd.address, + eur, + newSwapRateLong, + newSwapRateShort, + ); + const newStoredSwapRateLong = await protocol.currentSwapRates( + usd.address, + eur, + LONG, + ); + const newStoredSwapRateShort = await protocol.currentSwapRates( + usd.address, + eur, + SHORT, + ); + expect(newStoredSwapRateLong).to.be.bignumber.equals(newSwapRateLong); + expect(newStoredSwapRateShort).to.be.bignumber.equals(newSwapRateShort); + }); - it('allows only owner to set parameters', async () => { - await expectRevert( - protocol.setCurrentSwapRate(newSwapRate, { from: alice }), - messages.onlyOwner, - ); - }); + it('allows only owner to set parameters', async () => { + await expectRevert( + protocol.setCurrentSwapRateForPair( + usd.address, + eur, + newSwapRateLong, + newSwapRateShort, + { from: alice }, + ), + messages.onlyOwner, + ); + }); - it('does not allow zero values', async () => { - await expectRevert( - protocol.setCurrentSwapRate(0), - messages.settingZeroValueNotAllowed, - ); - }); + it('does not allow zero values', async () => { + await expectRevert( + protocol.setCurrentSwapRateForPair( + usd.address, + eur, + 0, + newSwapRateShort, + ), + messages.settingZeroValueNotAllowed, + ); + + await expectRevert( + protocol.setCurrentSwapRateForPair( + usd.address, + eur, + newSwapRateLong, + 0, + ), + messages.settingZeroValueNotAllowed, + ); }); }); @@ -557,7 +634,9 @@ contract('MarginFlowProtocol', accounts => { expectedPool: liquidityPool.address, expectedLeverage: leverage, leveragedHeldInQuote: leveragedHeldInEuro, - expectedSwapRate: initialSwapRate, + expectedSwapRate: leverage.isNeg() + ? initialSwapRateShort + : initialSwapRateLong, expectedTimeWhenOpened, receipt, }); @@ -616,7 +695,9 @@ contract('MarginFlowProtocol', accounts => { expectedPool: liquidityPool.address, expectedLeverage: leverage, leveragedHeldInQuote: leveragedHeldInEuro, - expectedSwapRate: initialSwapRate, + expectedSwapRate: leverage.isNeg() + ? initialSwapRateShort + : initialSwapRateLong, expectedTimeWhenOpened, receipt, }); @@ -649,7 +730,9 @@ contract('MarginFlowProtocol', accounts => { expectedPool: liquidityPool.address, expectedLeverage: leverage, leveragedHeldInQuote: leveragedHeldInEuro, - expectedSwapRate: initialSwapRate, + expectedSwapRate: leverage.isNeg() + ? initialSwapRateShort + : initialSwapRateLong, expectedTimeWhenOpened, receipt, baseToken: jpy, @@ -720,7 +803,9 @@ contract('MarginFlowProtocol', accounts => { expectedPool: liquidityPool.address, expectedLeverage: leverage, leveragedHeldInQuote: leveragedHeldInEuro, - expectedSwapRate: initialSwapRate, + expectedSwapRate: leverage.isNeg() + ? initialSwapRateShort + : initialSwapRateLong, expectedTimeWhenOpened: expectedTimeWhenOpened1, receipt: receipt1, baseToken: jpy, @@ -733,7 +818,9 @@ contract('MarginFlowProtocol', accounts => { expectedPool: liquidityPool2.address, expectedLeverage: leverage, leveragedHeldInQuote: leveragedHeldInEuro, - expectedSwapRate: initialSwapRate, + expectedSwapRate: leverage.isNeg() + ? initialSwapRateShort + : initialSwapRateLong, expectedTimeWhenOpened: expectedTimeWhenOpened2, receipt: receipt2, baseToken: jpy, diff --git a/test/margin/marginFlowProtocolSafety.ts b/test/margin/marginFlowProtocolSafety.ts index 46a6ccb..890e56c 100644 --- a/test/margin/marginFlowProtocolSafety.ts +++ b/test/margin/marginFlowProtocolSafety.ts @@ -126,7 +126,6 @@ contract('MarginFlowProtocolSafety', accounts => { moneyMarket.address, protocolSafety.address, liquidityPoolRegistry.address, - fromPercent(2), 1, 50, 1, @@ -195,7 +194,12 @@ contract('MarginFlowProtocolSafety', accounts => { from: liquidityProvider, }); await liquidityPoolRegistry.verifyPool(liquidityPool.address); - await protocol.addTradingPair(usd.address, eur); + await protocol.addTradingPair( + usd.address, + eur, + fromPercent(2), + fromPercent(2), + ); await oracle.feedPrice(usd.address, initialUsdPrice, { from: owner,