-
Notifications
You must be signed in to change notification settings - Fork 17
/
MorpherTradeEngine.sol
1273 lines (1118 loc) · 58.2 KB
/
MorpherTradeEngine.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//SPDX-License-Identifier: GPLv3
pragma solidity 0.8.11;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "./MorpherState.sol";
import "./MorpherToken.sol";
import "./MorpherStaking.sol";
import "./MorpherUserBlocking.sol";
import "./MorpherMintingLimiter.sol";
import "./MorpherAccessControl.sol";
// ----------------------------------------------------------------------------------
// Tradeengine of the Morpher platform
// Creates and processes orders, and computes the state change of portfolio.
// Needs writing/reading access to/from Morpher State. Order objects are stored locally,
// portfolios are stored in state.
// ----------------------------------------------------------------------------------
contract MorpherTradeEngine is Initializable, ContextUpgradeable {
MorpherState public morpherState;
/**
* Known Roles to Trade Engine
*/
bytes32 public constant ADMINISTRATOR_ROLE = keccak256("ADMINISTRATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bytes32 public constant POSITIONADMIN_ROLE = keccak256("POSITIONADMIN_ROLE"); //can set and modify positions
// ----------------------------------------------------------------------------
// Precision of prices and leverage
// ----------------------------------------------------------------------------
uint256 constant PRECISION = 10**8;
uint256 public orderNonce;
bytes32 public lastOrderId;
uint256 public deployedTimeStamp;
bool public escrowOpenOrderEnabled;
struct InterestRate {
uint256 validFrom;
uint256 rate;
}
struct PriceLock {
uint lockedPrice;
}
//we're locking positions in for this price at a market marketId;
mapping(bytes32 => PriceLock) public priceLockDeactivatedMarket;
// ----------------------------------------------------------------------------
// Order struct contains all order specific varibles. Variables are completed
// during processing of trade. State changes are saved in the order struct as
// well, since local variables would lead to stack to deep errors *sigh*.
// ----------------------------------------------------------------------------
struct order {
address userId;
bytes32 marketId;
uint256 closeSharesAmount;
uint256 openMPHTokenAmount;
bool tradeDirection; // true = long, false = short
uint256 liquidationTimestamp;
uint256 marketPrice;
uint256 marketSpread;
uint256 orderLeverage;
uint256 timeStamp;
uint256 orderEscrowAmount;
OrderModifier modifyPosition;
}
struct OrderModifier {
uint256 longSharesOrder;
uint256 shortSharesOrder;
uint256 balanceDown;
uint256 balanceUp;
uint256 newLongShares;
uint256 newShortShares;
uint256 newMeanEntryPrice;
uint256 newMeanEntrySpread;
uint256 newMeanEntryLeverage;
uint256 newLiquidationPrice;
}
mapping(bytes32 => order) public orders;
// ----------------------------------------------------------------------------
// Position struct records virtual futures
// ----------------------------------------------------------------------------
struct position {
uint256 lastUpdated;
uint256 longShares;
uint256 shortShares;
uint256 meanEntryPrice;
uint256 meanEntrySpread;
uint256 meanEntryLeverage;
uint256 liquidationPrice;
bytes32 positionHash;
}
// ----------------------------------------------------------------------------
// A portfolio is an address specific collection of postions
// ----------------------------------------------------------------------------
mapping(address => mapping(bytes32 => position)) public portfolio;
// ----------------------------------------------------------------------------
// Record all addresses that hold a position of a market, needed for clean stock splits
// ----------------------------------------------------------------------------
struct hasExposure {
uint256 maxMappingIndex;
mapping(address => uint256) index;
mapping(uint256 => address) addy;
}
mapping(bytes32 => hasExposure) public exposureByMarket;
mapping(uint256 => InterestRate) public interestRates;
uint256 public numInterestRates;
// ----------------------------------------------------------------------------
// Events
// Order created/processed events are fired by MorpherOracle.
// ----------------------------------------------------------------------------
event PositionLiquidated(
address indexed _address,
bytes32 indexed _marketId,
bool _longPosition,
uint256 _timeStamp,
uint256 _marketPrice,
uint256 _marketSpread
);
event OrderCancelled(
bytes32 indexed _orderId,
address indexed _address
);
event OrderIdRequested(
bytes32 _orderId,
address indexed _address,
bytes32 indexed _marketId,
uint256 _closeSharesAmount,
uint256 _openMPHTokenAmount,
bool _tradeDirection,
uint256 _orderLeverage
);
event OrderProcessed(
bytes32 _orderId,
uint256 _marketPrice,
uint256 _marketSpread,
uint256 _liquidationTimestamp,
uint256 _timeStamp,
uint256 _newLongShares,
uint256 _newShortShares,
uint256 _newAverageEntry,
uint256 _newAverageSpread,
uint256 _newAverageLeverage,
uint256 _liquidationPrice
);
event PositionUpdated(
address _userId,
bytes32 _marketId,
uint256 _timeStamp,
uint256 _newLongShares,
uint256 _newShortShares,
uint256 _newMeanEntryPrice,
uint256 _newMeanEntrySpread,
uint256 _newMeanEntryLeverage,
uint256 _newLiquidationPrice,
uint256 _mint,
uint256 _burn
);
event SetPosition(
bytes32 indexed positionHash,
address indexed sender,
bytes32 indexed marketId,
uint256 timeStamp,
uint256 longShares,
uint256 shortShares,
uint256 meanEntryPrice,
uint256 meanEntrySpread,
uint256 meanEntryLeverage,
uint256 liquidationPrice
);
event EscrowPaid(bytes32 orderId, address user, uint escrowAmount);
event EscrowReturned(bytes32 orderId, address user, uint escrowAmount);
event LinkState(address _address);
event LockedPriceForClosingPositions(bytes32 _marketId, uint256 _price);
event SetLeverageInterestRate(uint256 newInterestRate);
event LeverageInterestRateAdded(uint256 interestRate, uint256 validFromTimestamp);
function initialize(address _stateAddress, bool _escrowOpenOrderEnabled, uint256 _deployedTimestampOverride) public initializer {
ContextUpgradeable.__Context_init();
morpherState = MorpherState(_stateAddress);
escrowOpenOrderEnabled = _escrowOpenOrderEnabled;
deployedTimeStamp = _deployedTimestampOverride > 0 ? _deployedTimestampOverride : block.timestamp;
}
modifier onlyRole(bytes32 role) {
require(MorpherAccessControl(morpherState.morpherAccessControlAddress()).hasRole(role, _msgSender()), "MorpherTradeEngine: Permission denied.");
_;
}
// ----------------------------------------------------------------------------
// Administrative functions
// Set state address, get administrator address
// ----------------------------------------------------------------------------
function setMorpherState(address _stateAddress) public onlyRole(ADMINISTRATOR_ROLE) {
morpherState = MorpherState(_stateAddress);
emit LinkState(_stateAddress);
}
function setEscrowOpenOrderEnabled(bool _isEnabled) public onlyRole(ADMINISTRATOR_ROLE) {
escrowOpenOrderEnabled = _isEnabled;
}
/**
Interest rate
*/
function setLeverageInterestRate(uint256 _interestRate) public onlyRole(ADMINISTRATOR_ROLE) {
addInterestRate(_interestRate, block.timestamp);
}
/**
fallback function in case the old tradeengine asks for the current interest rate
*/
function interestRate() public view returns (uint256) {
//start with the last one, as its most likely the last active one, no need to run through the whole map
if(numInterestRates == 0) {
return 0;
}
for(uint256 i = numInterestRates - 1; i >= 0; i--) {
if(interestRates[i].validFrom <= block.timestamp) {
return interestRates[i].rate;
}
}
return 0;
}
function addInterestRate(uint _rate, uint _validFrom) public onlyRole(ADMINISTRATOR_ROLE) {
require(numInterestRates == 0 || interestRates[numInterestRates-1].validFrom < _validFrom, "MorpherTradeEngine: Interest Rate Valid From must be later than last interestRate");
require(_rate <= 100000000, "MorpherTradeEngine: Interest Rate cannot be larger than 100%");
require(_validFrom - 365 days <= block.timestamp, "MorpherTradeEngine: Interest Rate cannot start more than 1 year into the future");
//omitting rate sanity checks here. It should always be smaller than 100% (100000000) but I'll leave that to the common sense of the admin.
interestRates[numInterestRates].validFrom = _validFrom;
interestRates[numInterestRates].rate = _rate;
numInterestRates++;
emit LeverageInterestRateAdded(_rate, _validFrom);
}
function getInterestRate(uint256 _positionTimestamp) public view returns(uint256) {
uint256 sumInterestRatesWeighted = 0;
uint256 startingTimestamp = 0;
for(uint256 i = 0; i < numInterestRates; i++) {
if(i == numInterestRates-1 || interestRates[i+1].validFrom > block.timestamp) {
//reached last interest rate
sumInterestRatesWeighted = sumInterestRatesWeighted + (interestRates[i].rate * (block.timestamp - interestRates[i].validFrom));
if(startingTimestamp == 0) {
startingTimestamp = interestRates[i].validFrom;
}
break; //in case there are more in the future
} else {
//only take interest rates after the position was created
if(interestRates[i+1].validFrom > _positionTimestamp) {
sumInterestRatesWeighted = sumInterestRatesWeighted + (interestRates[i].rate * (interestRates[i+1].validFrom - interestRates[i].validFrom));
if(interestRates[i].validFrom <= _positionTimestamp) {
startingTimestamp = interestRates[i].validFrom;
}
}
}
}
uint interestRateInternal = sumInterestRatesWeighted / (block.timestamp - startingTimestamp);
return interestRateInternal;
}
function paybackEscrow(bytes32 _orderId) private {
//pay back the escrow to the user so he has it back on his balance/**
if(orders[_orderId].orderEscrowAmount > 0) {
//checks effects interaction
uint256 paybackAmount = orders[_orderId].orderEscrowAmount;
orders[_orderId].orderEscrowAmount = 0;
MorpherToken(morpherState.morpherTokenAddress()).mint(orders[_orderId].userId, paybackAmount);
emit EscrowReturned(_orderId, orders[_orderId].userId, paybackAmount);
}
}
function buildupEscrow(bytes32 _orderId, uint256 _amountInMPH) private {
if(escrowOpenOrderEnabled && _amountInMPH > 0) {
MorpherToken(morpherState.morpherTokenAddress()).burn(orders[_orderId].userId, _amountInMPH);
emit EscrowPaid(_orderId, orders[_orderId].userId, _amountInMPH);
orders[_orderId].orderEscrowAmount = _amountInMPH;
}
}
function validateClosedMarketOrderConditions(address _address, bytes32 _marketId, uint256 _closeSharesAmount, uint256 _openMPHTokenAmount, bool _tradeDirection ) internal view {
//markets active? Still tradeable?
if(_openMPHTokenAmount > 0) {
require(morpherState.getMarketActive(_marketId) == true, "MorpherTradeEngine: market unknown or currently not enabled for trading.");
} else {
//we're just closing a position, but it needs a forever price locked in if market is not active
//the user needs to close his complete position
if(morpherState.getMarketActive(_marketId) == false) {
require(getDeactivatedMarketPrice(_marketId) > 0, "MorpherTradeEngine: Can't close a position, market not active and closing price not locked");
if(_tradeDirection) {
//long
require(_closeSharesAmount == portfolio[_address][_marketId].longShares, "MorpherTradeEngine: Deactivated market order needs all shares to be closed");
} else {
//short
require(_closeSharesAmount == portfolio[_address][_marketId].longShares, "MorpherTradeEngine: Deactivated market order needs all shares to be closed");
}
}
}
}
//wrapper for stack too deep errors
function validateClosedMarketOrder(bytes32 _orderId) internal view {
validateClosedMarketOrderConditions(orders[_orderId].userId, orders[_orderId].marketId, orders[_orderId].closeSharesAmount, orders[_orderId].openMPHTokenAmount, orders[_orderId].tradeDirection);
}
// ----------------------------------------------------------------------------
// requestOrderId(address _address, bytes32 _marketId, bool _closeSharesAmount, uint256 _openMPHTokenAmount, bool _tradeDirection, uint256 _orderLeverage)
// Creates a new order object with unique orderId and assigns order information.
// Must be called by MorpherOracle contract.
// ----------------------------------------------------------------------------
function requestOrderId(
address _address,
bytes32 _marketId,
uint256 _closeSharesAmount,
uint256 _openMPHTokenAmount,
bool _tradeDirection,
uint256 _orderLeverage
) public onlyRole(ORACLE_ROLE) returns (bytes32 _orderId) {
require(_orderLeverage >= PRECISION, "MorpherTradeEngine: leverage too small. Leverage precision is 1e8");
require(_orderLeverage <= morpherState.getMaximumLeverage(), "MorpherTradeEngine: leverage exceeds maximum allowed leverage.");
validateClosedMarketOrderConditions(_address, _marketId, _closeSharesAmount, _openMPHTokenAmount, _tradeDirection);
//request limits
//@todo: fix request limit: 3 requests per block
/**
* The user can't partially close a position and open another one with MPH
*/
if(_openMPHTokenAmount > 0) {
if(_tradeDirection) {
//long
require(_closeSharesAmount == portfolio[_address][_marketId].shortShares, "MorpherTradeEngine: Can't partially close a position and open another one in opposite direction");
} else {
//short
require(_closeSharesAmount == portfolio[_address][_marketId].longShares, "MorpherTradeEngine: Can't partially close a position and open another one in opposite direction");
}
}
orderNonce++;
_orderId = keccak256(
abi.encodePacked(
_address,
block.number,
_marketId,
_closeSharesAmount,
_openMPHTokenAmount,
_tradeDirection,
_orderLeverage,
orderNonce
)
);
lastOrderId = _orderId;
orders[_orderId].userId = _address;
orders[_orderId].marketId = _marketId;
orders[_orderId].closeSharesAmount = _closeSharesAmount;
orders[_orderId].openMPHTokenAmount = _openMPHTokenAmount;
orders[_orderId].tradeDirection = _tradeDirection;
orders[_orderId].orderLeverage = _orderLeverage;
emit OrderIdRequested(
_orderId,
_address,
_marketId,
_closeSharesAmount,
_openMPHTokenAmount,
_tradeDirection,
_orderLeverage
);
/**
* put the money in escrow here if given MPH to open an order
* - also, can only close positions if in shares, so it will
* definitely trigger a mint there.
* The money must be put in escrow even though we have an existing position
*/
buildupEscrow(_orderId, _openMPHTokenAmount);
return _orderId;
}
// ----------------------------------------------------------------------------
// Getter functions for orders, shares, and positions
// ----------------------------------------------------------------------------
function getOrder(bytes32 _orderId) public view returns (
address _userId,
bytes32 _marketId,
uint256 _closeSharesAmount,
uint256 _openMPHTokenAmount,
uint256 _marketPrice,
uint256 _marketSpread,
uint256 _orderLeverage
) {
return(
orders[_orderId].userId,
orders[_orderId].marketId,
orders[_orderId].closeSharesAmount,
orders[_orderId].openMPHTokenAmount,
orders[_orderId].marketPrice,
orders[_orderId].marketSpread,
orders[_orderId].orderLeverage
);
}
function setDeactivatedMarketPrice(bytes32 _marketId, uint256 _price) public onlyRole(ADMINISTRATOR_ROLE) {
priceLockDeactivatedMarket[_marketId].lockedPrice = _price;
emit LockedPriceForClosingPositions(_marketId, _price);
}
function getDeactivatedMarketPrice(bytes32 _marketId) public view returns(uint256) {
return priceLockDeactivatedMarket[_marketId].lockedPrice;
}
// ----------------------------------------------------------------------------
// liquidate(bytes32 _orderId)
// Checks for bankruptcy of position between its last update and now
// Time check is necessary to avoid two consecutive / unorderded liquidations
// ----------------------------------------------------------------------------
function liquidate(bytes32 _orderId) private {
address _address = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _liquidationTimestamp = orders[_orderId].liquidationTimestamp;
if (_liquidationTimestamp > portfolio[_address][ _marketId].lastUpdated) {
if (portfolio[_address][_marketId].longShares > 0) {
setPosition(
_address,
_marketId,
orders[_orderId].timeStamp,
0,
portfolio[_address][ _marketId].shortShares,
0,
0,
PRECISION,
0);
emit PositionLiquidated(
_address,
_marketId,
true,
orders[_orderId].timeStamp,
orders[_orderId].marketPrice,
orders[_orderId].marketSpread
);
}
if (portfolio[_address][_marketId].shortShares > 0) {
setPosition(
_address,
_marketId,
orders[_orderId].timeStamp,
portfolio[_address][_marketId].longShares,
0,
0,
0,
PRECISION,
0
);
emit PositionLiquidated(
_address,
_marketId,
false,
orders[_orderId].timeStamp,
orders[_orderId].marketPrice,
orders[_orderId].marketSpread
);
}
}
}
// ----------------------------------------------------------------------------
// processOrder(bytes32 _orderId, uint256 _marketPrice, uint256 _marketSpread, uint256 _liquidationTimestamp, uint256 _timeStamp)
// ProcessOrder receives the price/spread/liqidation information from the Oracle and
// triggers the processing of the order. If successful, processOrder updates the portfolio state.
// Liquidation time check is necessary to avoid two consecutive / unorderded liquidations
// ----------------------------------------------------------------------------
function processOrder(
bytes32 _orderId,
uint256 _marketPrice,
uint256 _marketSpread,
uint256 _liquidationTimestamp,
uint256 _timeStampInMS
) public onlyRole(ORACLE_ROLE) returns (position memory) {
require(orders[_orderId].userId != address(0), "MorpherTradeEngine: unable to process, order has been deleted.");
require(_marketPrice > 0, "MorpherTradeEngine: market priced at zero. Buy order cannot be processed.");
require(_marketPrice >= _marketSpread, "MorpherTradeEngine: market price lower then market spread. Order cannot be processed.");
orders[_orderId].marketPrice = _marketPrice;
orders[_orderId].marketSpread = _marketSpread;
orders[_orderId].timeStamp = _timeStampInMS;
orders[_orderId].liquidationTimestamp = _liquidationTimestamp;
/**
* If the market is deactivated, then override the price with the locked in market price
* if the price wasn't locked in: error out.
*/
if(morpherState.getMarketActive(orders[_orderId].marketId) == false) {
validateClosedMarketOrder(_orderId);
orders[_orderId].marketPrice = getDeactivatedMarketPrice(orders[_orderId].marketId);
}
// Check if previous position on that market was liquidated
if (_liquidationTimestamp > portfolio[orders[_orderId].userId][ orders[_orderId].marketId].lastUpdated) {
liquidate(_orderId);
} else {
require(!MorpherUserBlocking(morpherState.morpherUserBlockingAddress()).userIsBlocked(orders[_orderId].userId), "MorpherTradeEngine: User is blocked from Trading");
}
paybackEscrow(_orderId);
if (orders[_orderId].tradeDirection) {
processBuyOrder(_orderId);
} else {
processSellOrder(_orderId);
}
address _address = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
delete orders[_orderId];
emit OrderProcessed(
_orderId,
_marketPrice,
_marketSpread,
_liquidationTimestamp,
_timeStampInMS,
portfolio[_address][_marketId].longShares,
portfolio[_address][_marketId].shortShares,
portfolio[_address][_marketId].meanEntryPrice,
portfolio[_address][_marketId].meanEntrySpread,
portfolio[_address][_marketId].meanEntryLeverage,
portfolio[_address][_marketId].liquidationPrice
);
return portfolio[_address][_marketId];
}
// ----------------------------------------------------------------------------
// function cancelOrder(bytes32 _orderId, address _address)
// Users or Administrator can delete pending orders before the callback went through
// ----------------------------------------------------------------------------
function cancelOrder(bytes32 _orderId, address _address) public onlyRole(ORACLE_ROLE) {
require(_address == orders[_orderId].userId || MorpherAccessControl(morpherState.morpherAccessControlAddress()).hasRole(ADMINISTRATOR_ROLE, _address), "MorpherTradeEngine: only Administrator or user can cancel an order.");
require(orders[_orderId].userId != address(0), "MorpherTradeEngine: unable to process, order does not exist.");
/**
* Pay back any escrow there
*/
paybackEscrow(_orderId);
delete orders[_orderId];
emit OrderCancelled(_orderId, _address);
}
// ----------------------------------------------------------------------------
// shortShareValue / longShareValue compute the value of a virtual future
// given current price/spread/leverage of the market and mean price/spread/leverage
// at the beginning of the trade
// ----------------------------------------------------------------------------
function shortShareValue(
uint256 _positionAveragePrice,
uint256 _positionAverageLeverage,
uint256 _positionTimeStampInMs,
uint256 _marketPrice,
uint256 _marketSpread,
uint256 _orderLeverage,
bool _sell
) public view returns (uint256 _shareValue) {
uint256 _averagePrice = _positionAveragePrice;
uint256 _averageLeverage = _positionAverageLeverage;
if (_positionAverageLeverage < PRECISION) {
// Leverage can never be less than 1. Fail safe for empty positions, i.e. undefined _positionAverageLeverage
_averageLeverage = PRECISION;
}
if (_sell == false) {
// New short position
// It costs marketPrice + marketSpread to build up a new short position
_averagePrice = _marketPrice;
// This is the average Leverage
_averageLeverage = _orderLeverage;
}
if (
getLiquidationPrice(_averagePrice, _averageLeverage, false, _positionTimeStampInMs) <= _marketPrice
) {
// Position is worthless
_shareValue = 0;
} else {
// The regular share value is 2x the entry price minus the current price for short positions.
_shareValue = _averagePrice * (PRECISION + _averageLeverage) / PRECISION;
_shareValue = _shareValue - _marketPrice * _averageLeverage / PRECISION;
if (_sell == true) {
// We have to reduce the share value by the average spread (i.e. the average expense to build up the position)
// and reduce the value further by the spread for selling.
_shareValue = _shareValue- _marketSpread * _averageLeverage / PRECISION;
uint256 _marginInterest = calculateMarginInterest(_averagePrice, _averageLeverage, _positionTimeStampInMs);
if (_marginInterest <= _shareValue) {
_shareValue = _shareValue - (_marginInterest);
} else {
_shareValue = 0;
}
} else {
// If a new short position is built up each share costs value + spread
_shareValue = _shareValue + (_marketSpread * (_orderLeverage) / (PRECISION));
}
}
return _shareValue;
}
function longShareValue(
uint256 _positionAveragePrice,
uint256 _positionAverageLeverage,
uint256 _positionTimeStampInMs,
uint256 _marketPrice,
uint256 _marketSpread,
uint256 _orderLeverage,
bool _sell
) public view returns (uint256 _shareValue) {
uint256 _averagePrice = _positionAveragePrice;
uint256 _averageLeverage = _positionAverageLeverage;
if (_positionAverageLeverage < PRECISION) {
// Leverage can never be less than 1. Fail safe for empty positions, i.e. undefined _positionAverageLeverage
_averageLeverage = PRECISION;
}
if (_sell == false) {
// New long position
// It costs marketPrice + marketSpread to build up a new long position
_averagePrice = _marketPrice;
// This is the average Leverage
_averageLeverage = _orderLeverage;
}
if (
_marketPrice <= getLiquidationPrice(_averagePrice, _averageLeverage, true, _positionTimeStampInMs)
) {
// Position is worthless
_shareValue = 0;
} else {
_shareValue = _averagePrice * (_averageLeverage - PRECISION) / (PRECISION);
// The regular share value is market price times leverage minus entry price times entry leverage minus one.
_shareValue = (_marketPrice * _averageLeverage / PRECISION) - _shareValue;
if (_sell == true) {
// We sell a long and have to correct the shareValue with the averageSpread and the currentSpread for selling.
_shareValue = _shareValue - (_marketSpread * _averageLeverage / PRECISION);
uint256 _marginInterest = calculateMarginInterest(_averagePrice, _averageLeverage, _positionTimeStampInMs);
if (_marginInterest <= _shareValue) {
_shareValue = _shareValue - (_marginInterest);
} else {
_shareValue = 0;
}
} else {
// We buy a new long position and have to pay the spread
_shareValue = _shareValue + (_marketSpread * (_orderLeverage) / (PRECISION));
}
}
return _shareValue;
}
// ----------------------------------------------------------------------------
// calculateMarginInterest(uint256 _averagePrice, uint256 _averageLeverage, uint256 _positionTimeStamp)
// Calculates the interest for leveraged positions
// ----------------------------------------------------------------------------
function calculateMarginInterest(uint256 _averagePrice, uint256 _averageLeverage, uint256 _positionTimeStampInMs) public view returns (uint256) {
uint _marginInterest;
if (_positionTimeStampInMs / 1000 < deployedTimeStamp) {
_positionTimeStampInMs = deployedTimeStamp / 1000;
}
_marginInterest = _averagePrice * (_averageLeverage - PRECISION);
_marginInterest = _marginInterest * (((block.timestamp - (_positionTimeStampInMs / 1000)) / 86400) + 1);
_marginInterest = ((_marginInterest * getInterestRate(_positionTimeStampInMs / 1000)) / PRECISION) / PRECISION;
return _marginInterest;
}
// ----------------------------------------------------------------------------
// processBuyOrder(bytes32 _orderId)
// Converts orders specified in virtual shares to orders specified in Morpher token
// and computes the number of short shares that are sold and long shares that are bought.
// long shares are bought only if the order amount exceeds all open short positions
// ----------------------------------------------------------------------------
function processBuyOrder(bytes32 _orderId) private {
if (orders[_orderId].closeSharesAmount > 0) {
//calcualte the balanceUp/down first
//then reopen the position with MPH amount
// Investment was specified in shares
if (orders[_orderId].closeSharesAmount <= portfolio[orders[_orderId].userId][ orders[_orderId].marketId].shortShares) {
// Partial closing of short position
orders[_orderId].modifyPosition.shortSharesOrder = orders[_orderId].closeSharesAmount;
} else {
// Closing of entire short position
orders[_orderId].modifyPosition.shortSharesOrder = portfolio[orders[_orderId].userId][ orders[_orderId].marketId].shortShares;
}
}
//calculate the long shares, but only if the old position is completely closed out (if none exist shortSharesOrder = 0)
if(
orders[_orderId].modifyPosition.shortSharesOrder == portfolio[orders[_orderId].userId][ orders[_orderId].marketId].shortShares &&
orders[_orderId].openMPHTokenAmount > 0
) {
orders[_orderId].modifyPosition.longSharesOrder = orders[_orderId].openMPHTokenAmount / (
longShareValue(
orders[_orderId].marketPrice,
orders[_orderId].orderLeverage,
block.timestamp * (1000),
orders[_orderId].marketPrice,
orders[_orderId].marketSpread,
orders[_orderId].orderLeverage,
false
));
}
// Investment equals number of shares now.
if (orders[_orderId].modifyPosition.shortSharesOrder > 0) {
closeShort(_orderId);
}
if (orders[_orderId].modifyPosition.longSharesOrder > 0) {
openLong(_orderId);
}
}
// ----------------------------------------------------------------------------
// processSellOrder(bytes32 _orderId)
// Converts orders specified in virtual shares to orders specified in Morpher token
// and computes the number of long shares that are sold and short shares that are bought.
// short shares are bought only if the order amount exceeds all open long positions
// ----------------------------------------------------------------------------
function processSellOrder(bytes32 _orderId) private {
if (orders[_orderId].closeSharesAmount > 0) {
//calcualte the balanceUp/down first
//then reopen the position with MPH amount
// Investment was specified in shares
if (orders[_orderId].closeSharesAmount <= portfolio[orders[_orderId].userId][ orders[_orderId].marketId].longShares) {
// Partial closing of long position
orders[_orderId].modifyPosition.longSharesOrder = orders[_orderId].closeSharesAmount;
} else {
// Closing of entire long position
orders[_orderId].modifyPosition.longSharesOrder = portfolio[orders[_orderId].userId][ orders[_orderId].marketId].longShares;
}
}
if(
orders[_orderId].modifyPosition.longSharesOrder == portfolio[orders[_orderId].userId][ orders[_orderId].marketId].longShares &&
orders[_orderId].openMPHTokenAmount > 0
) {
orders[_orderId].modifyPosition.shortSharesOrder = orders[_orderId].openMPHTokenAmount / (
shortShareValue(
orders[_orderId].marketPrice,
orders[_orderId].orderLeverage,
block.timestamp * (1000),
orders[_orderId].marketPrice,
orders[_orderId].marketSpread,
orders[_orderId].orderLeverage,
false
));
}
// Investment equals number of shares now.
if (orders[_orderId].modifyPosition.longSharesOrder > 0) {
closeLong(_orderId);
}
if (orders[_orderId].modifyPosition.shortSharesOrder > 0) {
openShort(_orderId);
}
}
// ----------------------------------------------------------------------------
// openLong(bytes32 _orderId)
// Opens a new long position and computes the new resulting average entry price/spread/leverage.
// Computation is broken down to several instructions for readability.
// ----------------------------------------------------------------------------
function openLong(bytes32 _orderId) private {
address _userId = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _newMeanSpread;
uint256 _newMeanLeverage;
// Existing position is virtually liquidated and reopened with current marketPrice
// orders[_orderId].modifyPosition.newMeanEntryPrice = orders[_orderId].marketPrice;
// _factorLongShares is a factor to adjust the existing longShares via virtual liqudiation and reopening at current market price
uint256 _factorLongShares = portfolio[_userId][ _marketId].meanEntryLeverage;
if (_factorLongShares < PRECISION) {
_factorLongShares = PRECISION;
}
_factorLongShares = _factorLongShares - (PRECISION);
_factorLongShares = _factorLongShares * (portfolio[_userId][ _marketId].meanEntryPrice) / (orders[_orderId].marketPrice);
if (portfolio[_userId][ _marketId].meanEntryLeverage > _factorLongShares) {
_factorLongShares = portfolio[_userId][ _marketId].meanEntryLeverage - (_factorLongShares);
} else {
_factorLongShares = 0;
}
uint256 _adjustedLongShares = _factorLongShares * (portfolio[_userId][ _marketId].longShares) / (PRECISION);
// _newMeanLeverage is the weighted leverage of the existing position and the new position
_newMeanLeverage = portfolio[_userId][ _marketId].meanEntryLeverage * (_adjustedLongShares);
_newMeanLeverage = _newMeanLeverage + (orders[_orderId].orderLeverage * (orders[_orderId].modifyPosition.longSharesOrder));
_newMeanLeverage = _newMeanLeverage / (_adjustedLongShares + (orders[_orderId].modifyPosition.longSharesOrder));
// _newMeanSpread is the weighted spread of the existing position and the new position
_newMeanSpread = portfolio[_userId][ _marketId].meanEntrySpread * (portfolio[_userId][ _marketId].longShares);
_newMeanSpread = _newMeanSpread + (orders[_orderId].marketSpread * (orders[_orderId].modifyPosition.longSharesOrder));
_newMeanSpread = _newMeanSpread / (_adjustedLongShares + (orders[_orderId].modifyPosition.longSharesOrder));
orders[_orderId].modifyPosition.balanceDown = orders[_orderId].modifyPosition.longSharesOrder * (orders[_orderId].marketPrice) + (
orders[_orderId].modifyPosition.longSharesOrder * (orders[_orderId].marketSpread) * (orders[_orderId].orderLeverage) / (PRECISION)
);
orders[_orderId].modifyPosition.balanceUp = 0;
orders[_orderId].modifyPosition.newLongShares = _adjustedLongShares + (orders[_orderId].modifyPosition.longSharesOrder);
orders[_orderId].modifyPosition.newShortShares = portfolio[_userId][ _marketId].shortShares;
orders[_orderId].modifyPosition.newMeanEntryPrice = orders[_orderId].marketPrice;
orders[_orderId].modifyPosition.newMeanEntrySpread = _newMeanSpread;
orders[_orderId].modifyPosition.newMeanEntryLeverage = _newMeanLeverage;
setPositionInState(_orderId);
}
// ----------------------------------------------------------------------------
// closeLong(bytes32 _orderId)
// Closes an existing long position. Average entry price/spread/leverage do not change.
// ----------------------------------------------------------------------------
function closeLong(bytes32 _orderId) private {
address _userId = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _newLongShares = portfolio[_userId][ _marketId].longShares - (orders[_orderId].modifyPosition.longSharesOrder);
uint256 _balanceUp = calculateBalanceUp(_orderId);
uint256 _newMeanEntry;
uint256 _newMeanSpread;
uint256 _newMeanLeverage;
if (orders[_orderId].modifyPosition.longSharesOrder == portfolio[_userId][ _marketId].longShares) {
_newMeanEntry = 0;
_newMeanSpread = 0;
_newMeanLeverage = PRECISION;
} else {
_newMeanEntry = portfolio[_userId][ _marketId].meanEntryPrice;
_newMeanSpread = portfolio[_userId][ _marketId].meanEntrySpread;
_newMeanLeverage = portfolio[_userId][ _marketId].meanEntryLeverage;
resetTimestampInOrderToLastUpdated(_orderId);
}
orders[_orderId].modifyPosition.balanceDown = 0;
orders[_orderId].modifyPosition.balanceUp = _balanceUp;
orders[_orderId].modifyPosition.newLongShares = _newLongShares;
orders[_orderId].modifyPosition.newShortShares = portfolio[_userId][ _marketId].shortShares;
orders[_orderId].modifyPosition.newMeanEntryPrice = _newMeanEntry;
orders[_orderId].modifyPosition.newMeanEntrySpread = _newMeanSpread;
orders[_orderId].modifyPosition.newMeanEntryLeverage = _newMeanLeverage;
setPositionInState(_orderId);
}
event ResetTimestampInOrder(bytes32 _orderId, uint oldTimestamp, uint newTimestamp);
function resetTimestampInOrderToLastUpdated(bytes32 _orderId) internal {
address userId = orders[_orderId].userId;
bytes32 marketId = orders[_orderId].marketId;
uint lastUpdated = portfolio[userId][ marketId].lastUpdated;
emit ResetTimestampInOrder(_orderId, orders[_orderId].timeStamp, lastUpdated);
orders[_orderId].timeStamp = lastUpdated;
}
// ----------------------------------------------------------------------------
// closeShort(bytes32 _orderId)
// Closes an existing short position. Average entry price/spread/leverage do not change.
// ----------------------------------------------------------------------------
function calculateBalanceUp(bytes32 _orderId) private view returns (uint256 _balanceUp) {
address _userId = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _shareValue;
if (orders[_orderId].tradeDirection == false) { //we are selling our long shares
_balanceUp = orders[_orderId].modifyPosition.longSharesOrder;
_shareValue = longShareValue(
portfolio[_userId][ _marketId].meanEntryPrice,
portfolio[_userId][ _marketId].meanEntryLeverage,
portfolio[_userId][ _marketId].lastUpdated,
orders[_orderId].marketPrice,
orders[_orderId].marketSpread,
portfolio[_userId][ _marketId].meanEntryLeverage,
true
);
} else { //we are going long, we are selling our short shares
_balanceUp = orders[_orderId].modifyPosition.shortSharesOrder;
_shareValue = shortShareValue(
portfolio[_userId][ _marketId].meanEntryPrice,
portfolio[_userId][ _marketId].meanEntryLeverage,
portfolio[_userId][ _marketId].lastUpdated,
orders[_orderId].marketPrice,
orders[_orderId].marketSpread,
portfolio[_userId][ _marketId].meanEntryLeverage,
true
);
}
return _balanceUp * (_shareValue);
}
function closeShort(bytes32 _orderId) private {
address _userId = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _newMeanEntry;
uint256 _newMeanSpread;
uint256 _newMeanLeverage;
uint256 _newShortShares = portfolio[_userId][ _marketId].shortShares - (orders[_orderId].modifyPosition.shortSharesOrder);
uint256 _balanceUp = calculateBalanceUp(_orderId);
if (orders[_orderId].modifyPosition.shortSharesOrder == portfolio[_userId][ _marketId].shortShares) {
_newMeanEntry = 0;
_newMeanSpread = 0;
_newMeanLeverage = PRECISION;
} else {
_newMeanEntry = portfolio[_userId][ _marketId].meanEntryPrice;
_newMeanSpread = portfolio[_userId][ _marketId].meanEntrySpread;
_newMeanLeverage = portfolio[_userId][ _marketId].meanEntryLeverage;
/**
* we need the timestamp of the old order for partial closes, not the new one
*/
resetTimestampInOrderToLastUpdated(_orderId);
}
orders[_orderId].modifyPosition.balanceDown = 0;
orders[_orderId].modifyPosition.balanceUp = _balanceUp;
orders[_orderId].modifyPosition.newLongShares = portfolio[orders[_orderId].userId][ orders[_orderId].marketId].longShares;
orders[_orderId].modifyPosition.newShortShares = _newShortShares;
orders[_orderId].modifyPosition.newMeanEntryPrice = _newMeanEntry;
orders[_orderId].modifyPosition.newMeanEntrySpread = _newMeanSpread;
orders[_orderId].modifyPosition.newMeanEntryLeverage = _newMeanLeverage;
setPositionInState(_orderId);
}
// ----------------------------------------------------------------------------
// openShort(bytes32 _orderId)
// Opens a new short position and computes the new resulting average entry price/spread/leverage.
// Computation is broken down to several instructions for readability.
// ----------------------------------------------------------------------------
function openShort(bytes32 _orderId) private {
address _userId = orders[_orderId].userId;
bytes32 _marketId = orders[_orderId].marketId;
uint256 _newMeanSpread;
uint256 _newMeanLeverage;
//
// Existing position is virtually liquidated and reopened with current marketPrice
// orders[_orderId].modifyPosition.newMeanEntryPrice = orders[_orderId].marketPrice;
// _factorShortShares is a factor to adjust the existing shortShares via virtual liqudiation and reopening at current market price
uint256 _factorShortShares = portfolio[_userId][ _marketId].meanEntryLeverage;
if (_factorShortShares < PRECISION) {
_factorShortShares = PRECISION;
}
_factorShortShares = _factorShortShares + (PRECISION);
_factorShortShares = _factorShortShares * (portfolio[_userId][ _marketId].meanEntryPrice) / (orders[_orderId].marketPrice);
if (portfolio[_userId][ _marketId].meanEntryLeverage < _factorShortShares) {