@@ -999,6 +999,64 @@ contract("Liquidator.js", function (accounts) {
999
999
assert . equal ( positionObject . tokensOutstanding . rawValue , convertSynthetic ( "5" ) ) ;
1000
1000
}
1001
1001
) ;
1002
+ versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
1003
+ "Liquidator will not liquidate itself with EOA sponsor" ,
1004
+ async function ( ) {
1005
+ // liquidator creates a position with 125 units of collateral, creating 100 synthetic tokens.
1006
+ await financialContract . create (
1007
+ { rawValue : convertCollateral ( "125" ) } ,
1008
+ { rawValue : convertSynthetic ( "100" ) } ,
1009
+ { from : liquidatorBot }
1010
+ ) ;
1011
+
1012
+ // sponsor2 creates a position with 150 units of collateral, creating 100 synthetic tokens.
1013
+ await financialContract . create (
1014
+ { rawValue : convertCollateral ( "150" ) } ,
1015
+ { rawValue : convertSynthetic ( "100" ) } ,
1016
+ { from : sponsor1 }
1017
+ ) ;
1018
+
1019
+ // Assume the price feed given to the liquidator is 1.2 this makes the liquidator under water but sponsor1
1020
+ // is above water. The liquidator bot should correctly identify this but it should not liquidate itself.
1021
+ // A price of 1.2 USD per token: debt * price * coltReq > debt for collateralized position.
1022
+ // liquidator: 100 * 1.3 * 1.2 > 125 [undercollateralized]
1023
+ // Sponsor1: 100 * 1.3 * 1.2 < 150 [sufficiently collateralized]
1024
+
1025
+ priceFeedMock . setCurrentPrice ( convertPrice ( "1.2" ) ) ;
1026
+ await liquidator . update ( ) ;
1027
+ await liquidator . liquidatePositions ( ) ;
1028
+ assert . equal ( spy . callCount , 1 ) ; // 1 info level events should be sent warning that the bot wont liquidate itself!.
1029
+
1030
+ // Should emit the right log message
1031
+ assert . isTrue ( spyLogIncludes ( spy , 0 , "The liquidator has an open position that is liquidatable" ) ) ;
1032
+
1033
+ // liquidatorBot should have all their collateral left and no liquidations.
1034
+ assert . deepStrictEqual ( await financialContract . getLiquidations ( liquidatorBot ) , [ ] ) ;
1035
+ assert . equal ( ( await financialContract . getCollateral ( liquidatorBot ) ) . rawValue , convertCollateral ( "125" ) ) ;
1036
+
1037
+ // Sponsor1 should have all their collateral left and no liquidations.
1038
+ assert . deepStrictEqual ( await financialContract . getLiquidations ( sponsor1 ) , [ ] ) ;
1039
+ assert . equal ( ( await financialContract . getCollateral ( sponsor1 ) ) . rawValue , convertCollateral ( "150" ) ) ;
1040
+
1041
+ // Run the liquidator again but this time at a price that will liquidatoe the sponsor. The liquidator
1042
+ // should still not liquidate itself, but should take out the underwater sponsor.
1043
+ priceFeedMock . setCurrentPrice ( convertPrice ( "1.3" ) ) ;
1044
+ await liquidator . update ( ) ;
1045
+ await liquidator . liquidatePositions ( ) ;
1046
+ assert . equal ( spy . callCount , 3 ) ;
1047
+
1048
+ // liquidatorBot should have all their collateral left and no liquidations.
1049
+ assert . deepStrictEqual ( await financialContract . getLiquidations ( liquidatorBot ) , [ ] ) ;
1050
+ assert . equal ( ( await financialContract . getCollateral ( liquidatorBot ) ) . rawValue , convertCollateral ( "125" ) ) ;
1051
+
1052
+ // Sponsor1 should be in a liquidation state with the bot as the liquidator.
1053
+ let liquidationObject = ( await financialContract . getLiquidations ( sponsor1 ) ) [ 0 ] ;
1054
+ assert . equal ( liquidationObject . sponsor , sponsor1 ) ;
1055
+ assert . equal ( liquidationObject . liquidator , liquidatorBot ) ;
1056
+ assert . equal ( liquidationObject . state , LiquidationStatesEnum . PRE_DISPUTE ) ;
1057
+ assert . equal ( liquidationObject . liquidatedCollateral , convertCollateral ( "150" ) ) ;
1058
+ }
1059
+ ) ;
1002
1060
describe ( "Partial liquidations" , function ( ) {
1003
1061
versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
1004
1062
"amount-to-liquidate > min-sponsor-tokens" ,
@@ -2031,7 +2089,7 @@ contract("Liquidator.js", function (accounts) {
2031
2089
}
2032
2090
}
2033
2091
) ;
2034
- versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
2092
+ versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] , true ) (
2035
2093
"Correctly deals with reserve being the same as collateral currency using DSProxy" ,
2036
2094
async function ( ) {
2037
2095
// create a new liquidator and set the reserve currency to the collateral currency.
@@ -2117,7 +2175,7 @@ contract("Liquidator.js", function (accounts) {
2117
2175
assert . isTrue ( spyLogIncludes ( spy , 3 , "Submitting a partial liquidation" ) ) ;
2118
2176
assert . isTrue ( spyLogIncludes ( spy , 4 , "Executed function on a freshly deployed library" ) ) ;
2119
2177
assert . isTrue ( spyLogIncludes ( spy , 5 , "Position has been liquidated" ) ) ;
2120
- assert . isTrue ( spyLogIncludes ( spy , 6 , "Insufficient balance to liquidate the minimum sponsor size " ) ) ;
2178
+ assert . isTrue ( spyLogIncludes ( spy , 6 , "The liquidator has an open position that is liquidatable " ) ) ;
2121
2179
}
2122
2180
) ;
2123
2181
versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
@@ -2280,7 +2338,7 @@ contract("Liquidator.js", function (accounts) {
2280
2338
assert . isTrue ( ( await reserveToken . balanceOf ( dsProxy . address ) ) . lt ( toBN ( toWei ( "0.000001" ) ) ) ) ;
2281
2339
}
2282
2340
) ;
2283
- versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] , true ) (
2341
+ versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
2284
2342
"Correctly respects max slippage parameters" ,
2285
2343
async function ( ) {
2286
2344
await reserveToken . mint ( dsProxy . address , toWei ( "1000000" ) , { from : contractCreator } ) ;
@@ -2433,6 +2491,72 @@ contract("Liquidator.js", function (accounts) {
2433
2491
assert . equal ( await getPoolSpotPrice ( ) , "1.0342" ) ;
2434
2492
}
2435
2493
) ;
2494
+ versionedIt ( [ { contractType : "any" , contractVersion : "any" } ] ) (
2495
+ "Liquidator will not liquidate itself with DSProxy sponsor" ,
2496
+ async function ( ) {
2497
+ // sponsor1 creates a position with 125 units of collateral, creating 100 synthetic tokens.
2498
+ await financialContract . create (
2499
+ { rawValue : convertCollateral ( "125" ) } ,
2500
+ { rawValue : convertSynthetic ( "100" ) } ,
2501
+ { from : sponsor1 }
2502
+ ) ;
2503
+
2504
+ // sponsor2 creates a position with 150 units of collateral, creating 100 synthetic tokens.
2505
+ await financialContract . create (
2506
+ { rawValue : convertCollateral ( "150" ) } ,
2507
+ { rawValue : convertSynthetic ( "100" ) } ,
2508
+ { from : sponsor2 }
2509
+ ) ;
2510
+
2511
+ // liquidatorBot creates NO position. This will happen atomically within 1tx by the dsProxy. Send reserve.
2512
+ await reserveToken . mint ( dsProxy . address , toWei ( "1000" ) , { from : contractCreator } ) ;
2513
+
2514
+ // Assume the price feed given to the liquidator is 1.1 this makes sponsor1 under water. The bot should
2515
+ // mint and liquidate in one transaction to take out the position.
2516
+ // Sponsor1: 100 * 1.3 * 1.1 > 125 [undercollateralized]
2517
+ // Sponsor2: 100 * 1.3 * 1.1 < 150 [sufficiently collateralized]
2518
+ // Note that at this point the GCR is 275/200 so the liquidator will mint at this rate.
2519
+
2520
+ priceFeedMock . setCurrentPrice ( convertPrice ( "1.1" ) ) ;
2521
+ await liquidator . update ( ) ;
2522
+ await liquidator . liquidatePositions ( ) ;
2523
+
2524
+ assert . equal ( spy . callCount , 3 ) ; // 3 info level events should be sent at the conclusion of the 1 liquidations.
2525
+ // 1 for the deployment of the DSProxy, 1 for the execution of the DSPRoxy ta and 1 for the liquidation.
2526
+
2527
+ // Sponsor1 should be in a liquidation state with the bot as the liquidator.
2528
+ let liquidationObject = ( await financialContract . getLiquidations ( sponsor1 ) ) [ 0 ] ;
2529
+ assert . equal ( liquidationObject . sponsor , sponsor1 ) ;
2530
+ assert . equal ( liquidationObject . liquidator , dsProxy . address ) ;
2531
+ assert . equal ( liquidationObject . state , LiquidationStatesEnum . PRE_DISPUTE ) ;
2532
+ assert . equal ( liquidationObject . liquidatedCollateral , convertCollateral ( "125" ) ) ;
2533
+
2534
+ // Next, assume that sponsor2 re-collateralizes their position taking it above the GCR.
2535
+ await financialContract . deposit ( { rawValue : convertCollateral ( "100" ) } , { from : sponsor2 } ) ;
2536
+
2537
+ // The liquidator's position has 100 units of debt and 137.5 units of collateral. At this ratio a price of
2538
+ // anything more than 1.145 would make the liquidator under water. If we set the price to 1.2 then the liquidator's
2539
+ // position should be liquidatable. However, the liquidator should never liquidate itself.
2540
+
2541
+ priceFeedMock . setCurrentPrice ( convertPrice ( "1.2" ) ) ;
2542
+ await liquidator . update ( ) ;
2543
+ await liquidator . liquidatePositions ( ) ;
2544
+
2545
+ // Should emit the right log message
2546
+ assert . isTrue ( spyLogIncludes ( spy , - 1 , "The liquidator has an open position that is liquidatable" ) ) ;
2547
+
2548
+ // dsProxy.address should have all their collateral left and no liquidations.
2549
+ assert . deepStrictEqual ( await financialContract . getLiquidations ( dsProxy . address ) , [ ] ) ;
2550
+ assert . equal (
2551
+ ( await financialContract . getCollateral ( dsProxy . address ) ) . rawValue ,
2552
+ convertCollateral ( "137.5" )
2553
+ ) ;
2554
+
2555
+ // Sponsor2 should have all their collateral left and no liquidations.
2556
+ assert . deepStrictEqual ( await financialContract . getLiquidations ( sponsor2 ) , [ ] ) ;
2557
+ assert . equal ( ( await financialContract . getCollateral ( sponsor2 ) ) . rawValue , convertCollateral ( "250" ) ) ;
2558
+ }
2559
+ ) ;
2436
2560
} ) ;
2437
2561
} ) ;
2438
2562
}
0 commit comments