diff --git a/lcov.info b/lcov.info index 2e405b3..cede330 100644 --- a/lcov.info +++ b/lcov.info @@ -1,9 +1,9 @@ TN: SF:src/ERC1155TokenReceiver.sol FN:5,ERC1155TokenReceiver.onERC1155Received -FNDA:4759,ERC1155TokenReceiver.onERC1155Received -DA:6,4759 -DA:6,4759 +FNDA:6095,ERC1155TokenReceiver.onERC1155Received +DA:6,6095 +DA:6,6095 FN:9,ERC1155TokenReceiver.onERC1155BatchReceived FNDA:0,ERC1155TokenReceiver.onERC1155BatchReceived DA:14,0 @@ -18,30 +18,30 @@ end_of_record TN: SF:src/InterestLib.sol FN:8,InterestLib.pow -FNDA:3935,InterestLib.pow -DA:9,111583 -DA:9,111583 -BRDA:9,0,0,3935 -BRDA:9,0,1,107648 -DA:10,3935 -DA:10,3935 -DA:11,107648 -DA:11,107648 -DA:11,107648 -BRDA:11,1,0,69606 -BRDA:11,1,1,38042 -DA:12,69606 -DA:12,69606 -DA:12,69606 -DA:13,69606 -DA:13,69606 -DA:13,69606 -DA:13,69606 -DA:15,38042 -DA:15,38042 -DA:15,38042 -DA:15,38042 -DA:15,38042 +FNDA:3928,InterestLib.pow +DA:9,111521 +DA:9,111521 +BRDA:9,0,0,3928 +BRDA:9,0,1,107593 +DA:10,3928 +DA:10,3928 +DA:11,107593 +DA:11,107593 +DA:11,107593 +BRDA:11,1,0,69454 +BRDA:11,1,1,38139 +DA:12,69454 +DA:12,69454 +DA:12,69454 +DA:13,69454 +DA:13,69454 +DA:13,69454 +DA:13,69454 +DA:15,38139 +DA:15,38139 +DA:15,38139 +DA:15,38139 +DA:15,38139 FNF:1 FNH:1 LF:6 @@ -51,316 +51,350 @@ BRH:4 end_of_record TN: SF:src/PolyLend.sol -FN:83,PolyLend. +FN:85,PolyLend. FNDA:273,PolyLend. -DA:84,273 -DA:84,273 -DA:85,273 -DA:85,273 -FN:88,PolyLend.getAmountOwed -FNDA:2102,PolyLend.getAmountOwed -DA:89,2102 -DA:89,2102 -DA:89,2102 -DA:90,2102 -DA:90,2102 -DA:90,2102 -FN:94,PolyLend.request -FNDA:9341,PolyLend.request -DA:98,9341 -DA:98,9341 -BRDA:98,0,0,1 -BRDA:98,0,1,9340 -DA:99,1 -DA:99,1 -DA:102,9340 -DA:102,9340 -DA:102,9340 -BRDA:102,1,0,1 -BRDA:102,1,1,9339 -DA:103,1 -DA:103,1 -DA:106,9339 -DA:106,9339 -BRDA:106,2,0,1 -BRDA:106,2,1,9338 -DA:107,1 -DA:107,1 -DA:110,9338 -DA:110,9338 -DA:111,9338 -DA:111,9338 -DA:113,9338 -DA:113,9338 -DA:114,9338 -DA:114,9338 -DA:116,9338 -DA:116,9338 -FN:120,PolyLend.cancelRequest +DA:86,273 +DA:86,273 +DA:87,273 +DA:87,273 +FN:90,PolyLend.getAmountOwed +FNDA:2098,PolyLend.getAmountOwed +DA:91,2098 +DA:91,2098 +DA:91,2098 +DA:92,2098 +DA:92,2098 +DA:92,2098 +FN:100,PolyLend.request +FNDA:10678,PolyLend.request +DA:104,10678 +DA:104,10678 +BRDA:104,0,0,1 +BRDA:104,0,1,10677 +DA:105,1 +DA:105,1 +DA:108,10677 +DA:108,10677 +DA:108,10677 +BRDA:108,1,0,1 +BRDA:108,1,1,10676 +DA:109,1 +DA:109,1 +DA:112,10676 +DA:112,10676 +BRDA:112,2,0,1 +BRDA:112,2,1,10675 +DA:113,1 +DA:113,1 +DA:116,10675 +DA:116,10675 +DA:117,10675 +DA:117,10675 +DA:119,10675 +DA:119,10675 +DA:120,10675 +DA:120,10675 +DA:122,10675 +DA:122,10675 +FN:126,PolyLend.cancelRequest FNDA:1608,PolyLend.cancelRequest -DA:121,1608 -DA:121,1608 -BRDA:121,3,0,536 -BRDA:121,3,1,1072 -DA:122,536 -DA:122,536 -DA:125,1072 -DA:125,1072 -FN:129,PolyLend.offer -FNDA:8539,PolyLend.offer -DA:130,8539 -DA:130,8539 -DA:130,8539 -BRDA:130,4,0,541 -BRDA:130,4,1,7998 -DA:131,541 -DA:131,541 -DA:134,7998 -DA:134,7998 -DA:134,7998 -BRDA:134,5,0,269 -BRDA:134,5,1,7729 -DA:135,269 -DA:135,269 -DA:138,7729 -DA:138,7729 -DA:138,7729 -BRDA:138,6,0,269 -BRDA:138,6,1,7460 -DA:139,269 -DA:139,269 -DA:142,7460 -DA:142,7460 -DA:142,7460 -DA:142,7191 -BRDA:142,7,0,538 -BRDA:142,7,1,6922 -DA:143,538 -DA:143,538 -DA:146,6922 -DA:146,6922 -DA:147,6922 -DA:147,6922 -DA:149,6922 -DA:149,6922 -DA:151,6922 -DA:151,6922 -DA:153,6922 -DA:153,6922 -FN:157,PolyLend.cancelOffer +DA:127,1608 +DA:127,1608 +BRDA:127,3,0,536 +BRDA:127,3,1,1072 +DA:128,536 +DA:128,536 +DA:131,1072 +DA:131,1072 +FN:139,PolyLend.offer +FNDA:9875,PolyLend.offer +DA:140,9875 +DA:140,9875 +DA:140,9875 +BRDA:140,4,0,541 +BRDA:140,4,1,9334 +DA:141,541 +DA:141,541 +DA:144,9334 +DA:144,9334 +DA:144,9334 +BRDA:144,5,0,269 +BRDA:144,5,1,9065 +DA:145,269 +DA:145,269 +DA:148,9065 +DA:148,9065 +DA:148,9065 +BRDA:148,6,0,269 +BRDA:148,6,1,8796 +DA:149,269 +DA:149,269 +DA:152,8796 +DA:152,8796 +DA:152,8796 +DA:152,8527 +BRDA:152,7,0,538 +BRDA:152,7,1,8258 +DA:153,538 +DA:153,538 +DA:156,8258 +DA:156,8258 +DA:157,8258 +DA:157,8258 +DA:159,8258 +DA:159,8258 +DA:161,8258 +DA:161,8258 +DA:163,8258 +DA:163,8258 +FN:167,PolyLend.cancelOffer FNDA:1355,PolyLend.cancelOffer -DA:158,1355 -DA:158,1355 -BRDA:158,8,0,542 -BRDA:158,8,1,813 -DA:159,542 -DA:159,542 -DA:162,813 -DA:162,813 -FN:166,PolyLend.accept -FNDA:5840,PolyLend.accept -DA:167,5840 -DA:167,5840 -DA:168,5840 -DA:168,5840 -DA:169,5840 -DA:169,5840 -DA:171,5840 -DA:171,5840 -BRDA:171,9,0,539 -BRDA:171,9,1,5301 -DA:172,539 -DA:172,539 -DA:175,5301 -DA:175,5301 -DA:175,5301 -BRDA:175,10,0,542 -BRDA:175,10,1,4759 -DA:176,542 -DA:176,542 -DA:179,4759 -DA:179,4759 -DA:180,4759 -DA:180,4759 -DA:182,4759 -DA:182,4759 -DA:183,4759 -DA:183,4759 -DA:184,4759 -DA:184,4759 -DA:187,4759 -DA:187,4759 -DA:200,4759 -DA:200,4759 -DA:203,4759 -DA:203,4759 -DA:206,4759 -DA:206,4759 -DA:209,4759 -DA:209,4759 -DA:211,4759 -DA:211,4759 -DA:213,4759 -DA:213,4759 -FN:217,PolyLend.call -FNDA:2933,PolyLend.call -DA:218,2933 -DA:218,2933 -DA:218,2933 -BRDA:218,11,0,267 -BRDA:218,11,1,2666 -DA:219,267 -DA:219,267 -DA:222,2666 -DA:222,2666 -BRDA:222,12,0,267 -BRDA:222,12,1,2399 -DA:223,267 -DA:223,267 -DA:226,2399 -DA:226,2399 -DA:226,2399 -BRDA:226,13,0,267 -BRDA:226,13,1,2132 -DA:227,267 -DA:227,267 -DA:230,2132 -DA:230,2132 -BRDA:230,14,0,267 -BRDA:230,14,1,1865 -DA:231,267 -DA:231,267 -DA:234,1865 -DA:234,1865 -DA:236,1865 -DA:236,1865 -FN:243,PolyLend.repay -FNDA:2594,PolyLend.repay -DA:244,2594 -DA:244,2594 -BRDA:244,15,0,518 -BRDA:244,15,1,2076 -DA:245,518 -DA:245,518 -DA:250,2076 -DA:250,2076 -BRDA:250,16,0,257 -BRDA:250,16,1,1301 -DA:251,1558 -DA:251,1558 -DA:251,1558 -BRDA:251,17,0,257 -BRDA:251,17,1,1301 -DA:252,257 -DA:252,257 -DA:257,518 -DA:257,518 -BRDA:257,18,0,259 -BRDA:257,18,1,259 -DA:258,259 -DA:258,259 -DA:263,1560 -DA:263,1560 -DA:263,1560 -DA:264,1560 -DA:264,1560 -DA:264,1560 -DA:267,1560 -DA:267,1560 -DA:269,1301 -DA:269,1301 -DA:274,1301 -DA:274,1301 -DA:276,1301 -DA:276,1301 -FN:280,PolyLend.transfer -FNDA:1357,PolyLend.transfer -DA:281,1357 -DA:281,1357 -DA:281,1357 -BRDA:281,19,0,272 -BRDA:281,19,1,1085 -DA:282,272 -DA:282,272 -DA:285,1085 -DA:285,1085 -BRDA:285,20,0,272 -BRDA:285,20,1,813 -DA:286,272 -DA:286,272 -DA:289,813 -DA:289,813 -DA:289,813 -BRDA:289,21,0,271 -BRDA:289,21,1,542 -DA:290,271 -DA:290,271 -DA:293,542 -DA:293,542 -DA:293,542 -DA:293,542 -DA:296,542 -DA:296,542 -BRDA:296,22,0,271 -BRDA:296,22,1,271 -DA:297,271 -DA:297,271 -DA:301,271 -DA:301,271 -DA:301,271 -DA:305,271 -DA:305,271 -DA:306,271 -DA:306,271 -DA:308,271 -DA:308,271 -DA:311,271 -DA:311,271 -DA:324,271 -DA:324,271 -DA:327,271 -DA:327,271 -DA:329,271 -DA:329,271 -FN:332,PolyLend._calculateAmountOwed -FNDA:3933,PolyLend._calculateAmountOwed -DA:337,3933 -DA:337,3933 -DA:337,3933 -DA:338,3933 -DA:338,3933 -DA:338,3933 -DA:338,3933 -FNF:11 -FNH:11 -LF:95 -LH:95 -BRF:46 -BRH:46 +DA:168,1355 +DA:168,1355 +BRDA:168,8,0,542 +BRDA:168,8,1,813 +DA:169,542 +DA:169,542 +DA:172,813 +DA:172,813 +FN:180,PolyLend.accept +FNDA:7176,PolyLend.accept +DA:181,7176 +DA:181,7176 +DA:182,7176 +DA:182,7176 +DA:183,7176 +DA:183,7176 +DA:185,7176 +DA:185,7176 +BRDA:185,9,0,539 +BRDA:185,9,1,6637 +DA:186,539 +DA:186,539 +DA:189,6637 +DA:189,6637 +DA:189,6637 +BRDA:189,10,0,542 +BRDA:189,10,1,6095 +DA:190,542 +DA:190,542 +DA:193,6095 +DA:193,6095 +DA:194,6095 +DA:194,6095 +DA:196,6095 +DA:196,6095 +DA:197,6095 +DA:197,6095 +DA:198,6095 +DA:198,6095 +DA:201,6095 +DA:201,6095 +DA:214,6095 +DA:214,6095 +DA:217,6095 +DA:217,6095 +DA:220,6095 +DA:220,6095 +DA:223,6095 +DA:223,6095 +DA:225,6095 +DA:225,6095 +DA:227,6095 +DA:227,6095 +FN:235,PolyLend.call +FNDA:3998,PolyLend.call +DA:236,3998 +DA:236,3998 +DA:236,3998 +BRDA:236,11,0,265 +BRDA:236,11,1,3733 +DA:237,265 +DA:237,265 +DA:240,3733 +DA:240,3733 +BRDA:240,12,0,265 +BRDA:240,12,1,3468 +DA:241,265 +DA:241,265 +DA:244,3468 +DA:244,3468 +DA:244,3468 +BRDA:244,13,0,265 +BRDA:244,13,1,3203 +DA:245,265 +DA:245,265 +DA:248,3203 +DA:248,3203 +BRDA:248,14,0,265 +BRDA:248,14,1,2938 +DA:249,265 +DA:249,265 +DA:252,2938 +DA:252,2938 +DA:254,2938 +DA:254,2938 +FN:265,PolyLend.repay +FNDA:2592,PolyLend.repay +DA:266,2592 +DA:266,2592 +BRDA:266,15,0,518 +BRDA:266,15,1,2074 +DA:267,518 +DA:267,518 +DA:272,2074 +DA:272,2074 +BRDA:272,16,0,257 +BRDA:272,16,1,1299 +DA:273,1556 +DA:273,1556 +DA:273,1556 +BRDA:273,17,0,257 +BRDA:273,17,1,1299 +DA:274,257 +DA:274,257 +DA:279,518 +DA:279,518 +BRDA:279,18,0,259 +BRDA:279,18,1,259 +DA:280,259 +DA:280,259 +DA:285,1558 +DA:285,1558 +DA:285,1558 +DA:286,1558 +DA:286,1558 +DA:286,1558 +DA:289,1558 +DA:289,1558 +DA:291,1299 +DA:291,1299 +DA:296,1299 +DA:296,1299 +DA:298,1299 +DA:298,1299 +FN:306,PolyLend.transfer +FNDA:1353,PolyLend.transfer +DA:307,1353 +DA:307,1353 +DA:307,1353 +BRDA:307,19,0,272 +BRDA:307,19,1,1081 +DA:308,272 +DA:308,272 +DA:311,1081 +DA:311,1081 +BRDA:311,20,0,271 +BRDA:311,20,1,810 +DA:312,271 +DA:312,271 +DA:315,810 +DA:315,810 +DA:315,810 +BRDA:315,21,0,270 +BRDA:315,21,1,540 +DA:316,270 +DA:316,270 +DA:319,540 +DA:319,540 +DA:319,540 +DA:319,540 +DA:322,540 +DA:322,540 +BRDA:322,22,0,270 +BRDA:322,22,1,270 +DA:323,270 +DA:323,270 +DA:327,270 +DA:327,270 +DA:327,270 +DA:331,270 +DA:331,270 +DA:332,270 +DA:332,270 +DA:334,270 +DA:334,270 +DA:337,270 +DA:337,270 +DA:350,270 +DA:350,270 +DA:353,270 +DA:353,270 +DA:355,270 +DA:355,270 +FN:362,PolyLend.reclaim +FNDA:1893,PolyLend.reclaim +DA:363,1893 +DA:363,1893 +DA:363,1893 +BRDA:363,23,0,543 +BRDA:363,23,1,1350 +DA:364,543 +DA:364,543 +DA:367,1350 +DA:367,1350 +BRDA:367,24,0,270 +BRDA:367,24,1,1080 +DA:368,270 +DA:368,270 +DA:371,1080 +DA:371,1080 +BRDA:371,25,0,270 +BRDA:371,25,1,810 +DA:372,270 +DA:372,270 +DA:375,810 +DA:375,810 +DA:375,810 +BRDA:375,26,0,270 +BRDA:375,26,1,540 +DA:376,270 +DA:376,270 +DA:380,540 +DA:380,540 +DA:385,540 +DA:385,540 +DA:387,540 +DA:387,540 +FN:394,PolyLend._calculateAmountOwed +FNDA:3926,PolyLend._calculateAmountOwed +DA:399,3926 +DA:399,3926 +DA:399,3926 +DA:400,3926 +DA:400,3926 +DA:400,3926 +DA:400,3926 +FNF:12 +FNH:12 +LF:106 +LH:106 +BRF:54 +BRH:54 end_of_record TN: SF:src/dev/DeployLib.sol FN:7,DeployLib._deployCode -FNDA:40,DeployLib._deployCode -DA:8,40 -DA:8,40 -DA:8,40 +FNDA:46,DeployLib._deployCode +DA:8,46 +DA:8,46 +DA:8,46 FN:11,DeployLib._deployCode -FNDA:40,DeployLib._deployCode -DA:12,40 -DA:12,40 -DA:12,40 -DA:14,40 -DA:14,40 +FNDA:46,DeployLib._deployCode +DA:12,46 +DA:12,46 +DA:12,46 +DA:14,46 +DA:14,46 FN:18,DeployLib.deployConditionalTokens -FNDA:40,DeployLib.deployConditionalTokens -DA:19,40 -DA:19,40 -DA:19,40 -DA:20,40 -DA:20,40 -DA:21,40 -DA:21,40 +FNDA:46,DeployLib.deployConditionalTokens +DA:19,46 +DA:19,46 +DA:19,46 +DA:20,46 +DA:20,46 +DA:21,46 +DA:21,46 FNF:3 FNH:3 LF:6 @@ -383,9 +417,9 @@ FNDA:0,USDC.decimals DA:16,0 DA:16,0 FN:19,USDC.mint -FNDA:19698,USDC.mint -DA:20,19698 -DA:20,19698 +FNDA:22367,USDC.mint +DA:20,22367 +DA:20,22367 FNF:4 FNH:1 LF:4 @@ -396,65 +430,65 @@ end_of_record TN: SF:src/test/PolyLendTestHelper.sol FN:25,PolyLendTestHelper.setUp -FNDA:40,PolyLendTestHelper.setUp -DA:26,40 -DA:26,40 -DA:27,40 -DA:27,40 -DA:28,40 -DA:28,40 -DA:30,40 -DA:30,40 -DA:31,40 -DA:31,40 -DA:32,40 -DA:32,40 -DA:33,40 -DA:33,40 -DA:35,40 -DA:35,40 -DA:36,40 -DA:36,40 -DA:38,40 -DA:38,40 -DA:38,40 -DA:39,40 -DA:39,40 -DA:41,40 -DA:41,40 -DA:41,40 -DA:42,40 -DA:42,40 +FNDA:46,PolyLendTestHelper.setUp +DA:26,46 +DA:26,46 +DA:27,46 +DA:27,46 +DA:28,46 +DA:28,46 +DA:30,46 +DA:30,46 +DA:31,46 +DA:31,46 +DA:32,46 +DA:32,46 +DA:33,46 +DA:33,46 +DA:35,46 +DA:35,46 +DA:36,46 +DA:36,46 +DA:38,46 +DA:38,46 +DA:38,46 +DA:39,46 +DA:39,46 +DA:41,46 +DA:41,46 +DA:41,46 +DA:42,46 +DA:42,46 FN:45,PolyLendTestHelper._mintConditionalTokens -FNDA:9339,PolyLendTestHelper._mintConditionalTokens -DA:46,9339 -DA:46,9339 -DA:47,9339 -DA:47,9339 -DA:48,9339 -DA:48,9339 -DA:50,9339 -DA:50,9339 -DA:50,9339 -DA:51,9339 -DA:51,9339 -DA:52,9339 -DA:52,9339 -DA:54,9339 -DA:54,9339 -DA:55,9339 -DA:55,9339 -DA:56,9339 -DA:56,9339 +FNDA:10676,PolyLendTestHelper._mintConditionalTokens +DA:46,10676 +DA:46,10676 +DA:47,10676 +DA:47,10676 +DA:48,10676 +DA:48,10676 +DA:50,10676 +DA:50,10676 +DA:50,10676 +DA:51,10676 +DA:51,10676 +DA:52,10676 +DA:52,10676 +DA:54,10676 +DA:54,10676 +DA:55,10676 +DA:55,10676 +DA:56,10676 +DA:56,10676 FN:59,PolyLendTestHelper._getRequest -FNDA:536,PolyLendTestHelper._getRequest -DA:60,536 -DA:60,536 -DA:61,536 -DA:61,536 -DA:63,536 -DA:63,536 -DA:63,536 +FNDA:537,PolyLendTestHelper._getRequest +DA:60,537 +DA:60,537 +DA:61,537 +DA:61,537 +DA:63,537 +DA:63,537 +DA:63,537 FN:71,PolyLendTestHelper._getOffer FNDA:540,PolyLendTestHelper._getOffer DA:72,540 @@ -464,22 +498,22 @@ DA:74,540 DA:74,540 DA:74,540 FN:77,PolyLendTestHelper._getLoan -FNDA:1584,PolyLendTestHelper._getLoan -DA:78,1584 -DA:78,1584 -DA:88,1584 -DA:88,1584 -DA:90,1584 -DA:90,1584 -DA:90,1584 +FNDA:1851,PolyLendTestHelper._getLoan +DA:78,1851 +DA:78,1851 +DA:88,1851 +DA:88,1851 +DA:90,1851 +DA:90,1851 +DA:90,1851 FN:103,PolyLendTestHelper._getNewRate -FNDA:813,PolyLendTestHelper._getNewRate -DA:104,813 -DA:104,813 -DA:104,813 -DA:104,813 -DA:104,813 -DA:104,813 +FNDA:810,PolyLendTestHelper._getNewRate +DA:104,810 +DA:104,810 +DA:104,810 +DA:104,810 +DA:104,810 +DA:104,810 FNF:6 FNH:6 LF:31 diff --git a/src/PolyLend.sol b/src/PolyLend.sol index 8fec701..50afe39 100644 --- a/src/PolyLend.sol +++ b/src/PolyLend.sol @@ -41,6 +41,7 @@ interface PolyLendEE { uint256 id, address borrower, uint256 positionId, uint256 collateralAmount, uint256 minimumDuration ); event LoanTransferred(uint256 oldId, uint256 newId, address newLender, uint256 newRate); + event LoanReclaimed(uint256 id); error CollateralAmountIsZero(); error InsufficientCollateralBalance(); @@ -58,6 +59,7 @@ interface PolyLendEE { error LoanIsCalled(); error MinimumDurationHasNotPassed(); error AuctionHasEnded(); + error AuctionHasNotEnded(); } /// @title PolyLend @@ -66,18 +68,35 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { /// @notice per second rate equal to roughly 1000% APY uint256 public constant MAX_INTEREST = InterestLib.ONE + InterestLib.ONE_THOUSAND_APY; + + /// @notice duration of the auction for transferring a loan uint256 public constant AUCTION_DURATION = 1 days; + + /// @notice buffer for payback time uint256 public constant PAYBACK_BUFFER = 1 minutes; + /// @notice The conditional tokens contract IConditionalTokens public immutable conditionalTokens; + + /// @notice The USDC token contract ERC20 public immutable usdc; + /// @notice The next id for a loan uint256 public nextLoanId = 0; + + /// @notice The next id for a request uint256 public nextRequestId = 0; + + /// @notice The next id for an offer uint256 public nextOfferId = 0; + /// @notice loans mapping mapping(uint256 => Loan) public loans; + + /// @notice requests mapping mapping(uint256 => Request) public requests; + + /// @notice offers mapping mapping(uint256 => Offer) public offers; constructor(address _conditionalTokens, address _usdc) { @@ -85,14 +104,22 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { usdc = ERC20(_usdc); } + /// @notice Get the amount owed on a loan + /// @param _loanId The id of the loan + /// @param _paybackTime The time at which the loan will be paid back + /// @return The amount owed on the loan function getAmountOwed(uint256 _loanId, uint256 _paybackTime) public view returns (uint256) { uint256 loanDuration = _paybackTime - loans[_loanId].startTime; return _calculateAmountOwed(loans[_loanId].loanAmount, loans[_loanId].rate, loanDuration); } + /*////////////////////////////////////////////////////////////// + REQUEST + //////////////////////////////////////////////////////////////*/ + /// @notice Submit a request for loan offers function request(uint256 _positionId, uint256 _collateralAmount, uint256 _minimumDuration) - public + external returns (uint256) { if (_collateralAmount == 0) { @@ -125,8 +152,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { requests[_requestId].borrower = address(0); } + /*////////////////////////////////////////////////////////////// + OFFER + //////////////////////////////////////////////////////////////*/ + /// @notice Submit a loan offer for a request - function offer(uint256 _requestId, uint256 _loanAmount, uint256 _rate) public returns (uint256) { + function offer(uint256 _requestId, uint256 _loanAmount, uint256 _rate) external returns (uint256) { if (requests[_requestId].borrower == address(0)) { revert InvalidRequest(); } @@ -162,8 +193,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { offers[_id].lender = address(0); } + /*////////////////////////////////////////////////////////////// + ACCEPT + //////////////////////////////////////////////////////////////*/ + /// @notice Accept a loan offer - function accept(uint256 _offerId) public returns (uint256) { + function accept(uint256 _offerId) external returns (uint256) { uint256 requestId = offers[_offerId].requestId; address borrower = requests[requestId].borrower; address lender = offers[_offerId].lender; @@ -213,8 +248,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { return loanId; } + /*////////////////////////////////////////////////////////////// + CALL + //////////////////////////////////////////////////////////////*/ + /// @notice Call a loan - function call(uint256 _loanId) public { + function call(uint256 _loanId) external { if (loans[_loanId].borrower == address(0)) { revert InvalidLoan(); } @@ -236,11 +275,15 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { emit LoanCalled(_loanId, block.timestamp); } + /*////////////////////////////////////////////////////////////// + REPAY + //////////////////////////////////////////////////////////////*/ + /// @notice Repay a loan /// @notice It is possible that the the block.timestamp will differ /// @notice from the time that the transaction is submitted to the /// @notice block when it is mined. - function repay(uint256 _loanId, uint256 _repayTimestamp) public { + function repay(uint256 _loanId, uint256 _repayTimestamp) external { if (loans[_loanId].borrower != msg.sender) { revert OnlyBorrower(); } @@ -276,8 +319,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { emit LoanRepaid(_loanId); } + /*////////////////////////////////////////////////////////////// + TRANSFER + //////////////////////////////////////////////////////////////*/ + /// @notice Transfer a called loan to a new lender - function transfer(uint256 _loanId, uint256 _newRate) public { + function transfer(uint256 _loanId, uint256 _newRate) external { if (loans[_loanId].borrower == address(0)) { revert InvalidLoan(); } @@ -329,6 +376,42 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver { emit LoanTransferred(_loanId, loanId, msg.sender, _newRate); } + /*////////////////////////////////////////////////////////////// + RECLAIM + //////////////////////////////////////////////////////////////*/ + + function reclaim(uint256 _loanId) external { + if (loans[_loanId].borrower == address(0)) { + revert InvalidLoan(); + } + + if (loans[_loanId].lender != msg.sender) { + revert OnlyLender(); + } + + if (loans[_loanId].callTime == 0) { + revert LoanIsNotCalled(); + } + + if (block.timestamp <= loans[_loanId].callTime + AUCTION_DURATION) { + revert AuctionHasNotEnded(); + } + + // transfer the borrower's collateral to the lender + conditionalTokens.safeTransferFrom( + address(this), msg.sender, loans[_loanId].positionId, loans[_loanId].collateralAmount, "" + ); + + // cancel the loan + loans[_loanId].borrower = address(0); + + emit LoanReclaimed(_loanId); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + function _calculateAmountOwed(uint256 _loanAmount, uint256 _rate, uint256 _loanDuration) internal pure diff --git a/src/test/Reclaim.t.sol b/src/test/Reclaim.t.sol new file mode 100644 index 0000000..32c1dbb --- /dev/null +++ b/src/test/Reclaim.t.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {PolyLendTestHelper, Loan} from "./PolyLendTestHelper.sol"; + +contract PolyLendReclaimTest is PolyLendTestHelper { + uint256 rate; + + function setUp() public override { + super.setUp(); + } + + function _setUp( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint256 _minimumDuration, + uint256 _duration + ) internal returns (uint256) { + vm.assume(_collateralAmount > 0); + + rate = bound(_rate, 10 ** 18 + 1, polyLend.MAX_INTEREST()); + + _mintConditionalTokens(borrower, _collateralAmount, positionId0); + usdc.mint(lender, _loanAmount); + + vm.startPrank(borrower); + conditionalTokens.setApprovalForAll(address(polyLend), true); + uint256 requestId = polyLend.request(positionId0, _collateralAmount, _minimumDuration); + vm.stopPrank(); + + vm.startPrank(lender); + usdc.approve(address(polyLend), _loanAmount); + uint256 offerId = polyLend.offer(requestId, _loanAmount, rate); + vm.stopPrank(); + + vm.startPrank(borrower); + uint256 loanId = polyLend.accept(offerId); + vm.stopPrank(); + + vm.warp(block.timestamp + _duration); + + vm.startPrank(lender); + polyLend.call(loanId); + vm.stopPrank(); + + return loanId; + } + + function test_PolyLendTransferTest_reclaim( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint32 _minimumDuration, + uint256 _duration, + uint256 _auctionLength + ) public { + vm.assume(_minimumDuration <= 60 days); + + uint256 loanId; + uint256 callTime; + + { + uint256 duration = bound(_duration, _minimumDuration, 60 days); + uint256 auctionLength = bound(_auctionLength, polyLend.AUCTION_DURATION() + 1, type(uint32).max); + loanId = _setUp(_collateralAmount, _loanAmount, _rate, _minimumDuration, duration); + + callTime = block.timestamp; + vm.warp(block.timestamp + auctionLength); + } + + vm.startPrank(lender); + vm.expectEmit(); + emit LoanReclaimed(loanId); + polyLend.reclaim(loanId); + vm.stopPrank(); + + Loan memory loan = _getLoan(loanId); + + assertEq(loan.borrower, address(0)); + + assertEq(conditionalTokens.balanceOf(lender, positionId0), _collateralAmount); + } + + function test_revert_PolyLendTransferTest_reclaim_InvalidLoan_loanDoesNotExist(uint128 _loanId) public { + vm.startPrank(lender); + vm.expectRevert(InvalidLoan.selector); + polyLend.reclaim(_loanId); + vm.stopPrank(); + } + + function test_revert_PolyLendTransferTest_reclaim_InvalidLoan_alreadyReclaimed( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint32 _minimumDuration, + uint256 _duration, + uint256 _auctionLength + ) public { + vm.assume(_minimumDuration <= 60 days); + + uint256 loanId; + uint256 callTime; + + { + uint256 duration = bound(_duration, _minimumDuration, 60 days); + uint256 auctionLength = bound(_auctionLength, polyLend.AUCTION_DURATION() + 1, type(uint32).max); + loanId = _setUp(_collateralAmount, _loanAmount, _rate, _minimumDuration, duration); + + callTime = block.timestamp; + vm.warp(block.timestamp + auctionLength); + } + + vm.startPrank(lender); + polyLend.reclaim(loanId); + vm.expectRevert(InvalidLoan.selector); + polyLend.reclaim(loanId); + vm.stopPrank(); + } + + function test_revert_PolyLendTransferTest_reclaim_OnlyLender( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint32 _minimumDuration, + uint256 _duration, + uint256 _auctionLength, + address _caller + ) public { + vm.assume(_minimumDuration <= 60 days); + vm.assume(_caller != lender); + + uint256 loanId; + uint256 callTime; + + { + uint256 duration = bound(_duration, _minimumDuration, 60 days); + uint256 auctionLength = bound(_auctionLength, polyLend.AUCTION_DURATION() + 1, type(uint32).max); + loanId = _setUp(_collateralAmount, _loanAmount, _rate, _minimumDuration, duration); + + callTime = block.timestamp; + vm.warp(block.timestamp + auctionLength); + } + + vm.startPrank(_caller); + vm.expectRevert(OnlyLender.selector); + polyLend.reclaim(loanId); + vm.stopPrank(); + } + + function test_revert_PolyLendTransferTest_reclaim_LoanIsNotCalled( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint32 _minimumDuration, + uint256 _duration, + uint256 _auctionLength + ) public { + vm.assume(_minimumDuration <= 60 days); + + uint256 loanId; + + { + uint256 duration = bound(_duration, _minimumDuration, 60 days); + uint256 auctionLength = bound(_auctionLength, polyLend.AUCTION_DURATION() + 1, type(uint32).max); + vm.assume(_collateralAmount > 0); + + rate = bound(_rate, 10 ** 18 + 1, polyLend.MAX_INTEREST()); + + _mintConditionalTokens(borrower, _collateralAmount, positionId0); + usdc.mint(lender, _loanAmount); + + vm.startPrank(borrower); + conditionalTokens.setApprovalForAll(address(polyLend), true); + uint256 requestId = polyLend.request(positionId0, _collateralAmount, _minimumDuration); + vm.stopPrank(); + + vm.startPrank(lender); + usdc.approve(address(polyLend), _loanAmount); + uint256 offerId = polyLend.offer(requestId, _loanAmount, rate); + vm.stopPrank(); + + vm.startPrank(borrower); + loanId = polyLend.accept(offerId); + vm.stopPrank(); + + vm.warp(block.timestamp + duration + auctionLength); + } + + vm.startPrank(lender); + vm.expectRevert(LoanIsNotCalled.selector); + polyLend.reclaim(loanId); + vm.stopPrank(); + } + + function test_revert_PolyLendTransferTest_reclaim_AuctionHasNotEnded( + uint128 _collateralAmount, + uint128 _loanAmount, + uint256 _rate, + uint32 _minimumDuration, + uint256 _duration, + uint256 _auctionLength + ) public { + vm.assume(_minimumDuration <= 60 days); + + uint256 loanId; + uint256 callTime; + + { + uint256 duration = bound(_duration, _minimumDuration, 60 days); + uint256 auctionLength = bound(_auctionLength, 0, polyLend.AUCTION_DURATION()); + loanId = _setUp(_collateralAmount, _loanAmount, _rate, _minimumDuration, duration); + + callTime = block.timestamp; + vm.warp(block.timestamp + auctionLength); + } + + vm.startPrank(lender); + vm.expectRevert(AuctionHasNotEnded.selector); + polyLend.reclaim(loanId); + vm.stopPrank(); + } +} diff --git a/src/test/Transfer.t.sol b/src/test/Transfer.t.sol index 96f89b3..2fa56d8 100644 --- a/src/test/Transfer.t.sol +++ b/src/test/Transfer.t.sol @@ -96,6 +96,9 @@ contract PolyLendTransferTest is PolyLendTestHelper { assertEq(newLoan.startTime, block.timestamp); assertEq(newLoan.minimumDuration, 0); assertEq(newLoan.callTime, 0); + + assertEq(usdc.balanceOf(lender), amountOwed); + assertEq(usdc.balanceOf(newLender), 0); } function test_revert_PolyLendTransferTest_transfer_InvalidLoan(uint256 _loanId, uint256 _newRate) public {