From 187757c633db3bab99adecb12753fbd6cccae098 Mon Sep 17 00:00:00 2001 From: PlayJoker <5438883+PlayJok3r@users.noreply.github.com> Date: Tue, 26 Nov 2024 07:01:14 -0800 Subject: [PATCH 1/4] added incremental audit report --- audit/spearbit-incremental-Nov-2024.pdf | Bin 0 -> 65787 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/spearbit-incremental-Nov-2024.pdf diff --git a/audit/spearbit-incremental-Nov-2024.pdf b/audit/spearbit-incremental-Nov-2024.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3591f3928bf733d3ae6bb6333e5cb13bc82ed73 GIT binary patch literal 65787 zcmbrGWl&t(wzeA@cMBfe-QC@t;10oEf@^Sh2<{NvCBa>TCAhmg1nrM~&c5f$rt0o{ zzOL%7)zyFIusNQ0%(a?aNlb#CnSmXSd~&vb9*&KPh>6JF*b0u14~|jJ%+B1!f{2x! ziRhm%I7SIe8y7PtB1Q=tBNsCUVS;bx^gu%0C3;{c| z@k_g&@KARc;)di^l+dc90VKV=l^`FlOp2wi-C*pETfaIx-nRe6etpuc51zL>oISn5|D{ zhsOv46x`g&NW*o6hA1H^$bB#Xy^(zi(WC8pnfEsyJJ{(N4P*u30`G| zB*!ho&(ufk2BXF^baP(F#XD|Vr{E(2)O-XxNYTJis$04AP*KlNg}clcHyw+C6waN}yA;V0n}^ zo3Zz2Dg;~+jZmi|J-Uj2(z*sJYv)VCnc12C>FC$1KZ3;0`PXQ%v2yq6X2lF9R26mG4+pSODb zxcq4z;^r$ng`|9a;ov)jWv&rOOaX%hr_>>?f?C8aqbkfM>JiK_?6DQ-*v;6vn}by3 zlSQE&;7L^g6GYT?t<;mE8Kh99XOZ!hHpq6l5I0;6HJ8u#`p#$-&-WlY*chTvB8@q} z$UBKhnQVrwwWebktBK=8%{WLkp29SoyvY6mMO(0B^*A2K{2*$LSaxs7P^hWyWeF`P zUwWhcq9bOD{cKdY1KQmW`3g1@uvr&Ls)>wOOipI)Y^{xV3T?Xg2s7@Rr<>G&VQlXJ zbqLDOgfjeCoK`}XsMRn#6l7K)ew)(9VRo0;KeDXt%-MHTRTX*rBAv4{Lq{-!PA)*D zr=_9Qe5J^NQG&-w82z|to6&S?UOG=GkGcio0ckis*}?SZd=vW2-qsa-;kFl|MT@s* zYqm&`P5831;J_-jF~Ha6h4lqnI;)|W<>qdc;bH6-MxOOJs_Te8;GEiCj2etULsb}I zdWx1ZmGP<{`S$oQ;;%`-!Ti@GU}524|GOnysihUW#ewE~W{{WK@9A$Teg{SezCAxq zAr)tWJ0D7h+|bvQ9xoP4)w=ccpjI1ExBS6t%1sb^SF3`?w?a0D|LIb7-*!xI-S&Ie z-Y=-Vq}dG21?G zuJ|Mk(vp_fYV-zpwlCA(i`fSFO?^!XQi`syR5}%Bg&WN}Ig)%JY;p=;Rmdruh7!xo(Tx+Q=U#Zpv9i>m*Jor80M$G&iqJa-}(DBUdw= zUyAe>;*X`EJv=BS;zqh}gJprqTcRaH8la`GA%i9DiMP4*-?SszPZ80l#uO0)O9z3h zGe6mMw@xfcr5RGFNz=t)qM$?oa|C%-(;yVPM>|CDpdN#gaTw?qYqdDvr@x6jV*#~ zrxG$iyRtzmN==81sn!%O)SVneYUEa@(h2FP!ad8MS~P8It)9K@T{H=(#1(n(LYK0h z;{rqs5omC0Uj@=w^{H!Bb?hUt*$p(t=ScMMU+d_hV_}Yk92{UBGzG#^b5QfGf{EO# zsCUkZ0ueW{um*f-;JBNt|DsmGiVhGMQlYNG zB}94ZBic)i#n}uqjH5Svcc)4UCd*d)F*(w+nyy3PAY#*@t_!p=NqTF zFqyhwh(^9ePFPmJrLb!O0pg80WnM&p^7_(v^v{$gc!Gst1*CS?EG8x?;5LW5y_w5L z+Bm31yl=+CQ468*kX>gX^t>*bMP;nn=l-ef`uKtMW0`Z2^GDD5W0E9nUi(7pV~C)~ zjnf%DnDH+7DX=~9-6FGnbE9MRla=aDXnjQIumX~s?BkwavJsO%f*=;7?>0{NAkjRI zB^{U<6aD*J4F;H19X7hAIL+g8%$b#Fp6C>$x{xgrl`E-6V2_Ma9*ojD`@4+@9)#1g zv!5E-)T`YXp4^L@Th2G)+aHc08^wK5lQ}H~%?VVAgaAK+%IXqa>vtzW(d zU3p7iT_yTqkJq5Ii92Yg1LeQLyzk0EI={AW@X0w3TppTYHa=@6LYyT%v-G4s1iD() z;5`soYoa_b8r)j<^LLhUUCp$759ukqI+EsMw#)4@hCpd5NqvXf?St75g4D%$H0y3U z?zF0J2Q(caMW=Le2;O-4gGq=8+xQsZlkLmbz$=&LfkkOKh$+pL$`68tAr^y2oqAm& zam8{y@*%tgIOJQB#A(j(om)m|sJaBRdGhbeaIULwGBs|HA@y3`MNZfcfCswMKBQ>> z3f-d3wWEERWZ*%fdI? z56=$_Ye%1qI&@ynlegq#KFSx&C2>Gvt|!p%bONeB%EB!LWi1CSQJ7$^k?Ssi>AAFoqpG?1h#nA}QL1 z`BuDp)SllTp2<^!JW9f8TQz&0VetqD#-Y$9zN+V59N+9a35g(nQK_YTGFe`XgbO^A zo%dj`-;?1jt;8t7?sS{-v2$FWT;J52Y-qz!Ga?>>c4GKF|2>?N>vU~8P^sJS}9H3m{A&`kwl}c69Ip-u?(5I3uu9REA51^KR+YHIYLs=E zkGjJ8ElQl(WaA3uqUEg8T}z7^nU>#1L?!OgiVB4(nHSAlNjo zh9dZ-&y$bIxLoW{OR~c%_#T~Iho2)-yCz~JkyO+B;j2G!X%<&Jv*P}!M%|&fpbx(F z+}8|&M)Ie7IxY3D)dRl}@=4*-SYw&`i6mgQxv7DiJzBNfNScFr@g++K&G-r({-(T8HMbgXGa1SeIsdSRBe-ozd~*!u|a~- zOcrc%j_uzN*444hYyI53d#8iHSE=h4%H6@w+qQdiH5lDXT3yNhJmVaQ^OHf~=)F5d ziWjY(WuPOroAmr zqylDu`4gU}PQCnJL5Y*?ub{-m#r(Hvxm{h}ewhoQ{YUk=Ex*vo(B&ulZ;=$XwJY?~ z!i@6B5tX&UjYR=uig7OwD)}E1(xD(10T<#d(ii+HWUjTXFKOaITJaWPQZBB#pv4LX zhDf@R2sTCJ_VrIKpoTHl znY*LvjJx$X3ERaF4NfmoNs~O}W-(Q(46Hho9(w&b)Kxv}BZWFgn!|;7>2Xw9Eyuz5 z=jJ8J>RDgTf3Wa7c&V*2NJ!ZQHupQY%rW=Mw<&y07&tqoHvJKeO?DO}7+kIo-UJu5 z>irVeGNenVmC|GuTBvr0uj%byJL=o%m zpS%Y)y_9Rlzob>WSK8i=Y?r#CaDXE4bXIHzaA8}diB|DXe9z=D)**Jw%O0-aa0O@Y zFv@NBkz%2%F*TK5S2p(2mz$M~tB9T0u26k&kRR6}waq>&3{y53RB}m?s5@e26;Ir#V$?*(Moo5AQb_ zdKTN_-+KQXm(`hHATL{f%(tWPNmM4Vh1*4Te;{eQ#B%LQ`lO4^3o4!^(ktPn!J>^Y zED2(%8|OVvauw<4>l%G@xm=T$9Z$^p`P>@P`qBW?d@1bu-*Q3cF^?Vql@@;X6AuUH z&q;8Q)9R0jCCu~<7K+fx`>hR|A={8=vHgkWp_{O(FNzgh<#ZVikt^E-$fwd8fqQoxKhVUoBNW`G#<}V($7ayN+E7TRL)uw_XSk^j|(~ zhcGbYy(iEl^GN=M7*)by`vq!vc`_<9i`zthX1D9fK25CZwAUJXS>FUKA*UxlHnHi6 ze7@Nj+W`9#c-GG%2uyl`+7~=L4etGJocHA==*|UmZT;lK^mmwRURcG;W3UAblpj#< z$1mBeb15T>{kt)gF3}4(Gk$&=lyIH;YXNh;F`QuLX8Tj`{_n$yN=@0sB@VQXM~(d^ zmt^rPc`N%>cgk29E4{?B5#k9j5@J|(Zz>|LFT>Sqb}qm|Q_sxG#onYY3E=+VT(t+t z0YtruKgcM6~`suXO6|0J$*-i_UH zIZJpgDLA71{;}D?5o1FYT|6dkL)y#k=0WDrc+tLvM(NDddRBw7A2Vc^>U92I7dnJk*(xv z@ioo*vzI@lV?w|T%hq4o`ND6z*j{epEQBS?MBN!>f{>9AQXx6Pq%29;*M#)U>HTSc z&YjFkSvwJ`o}G=X^xQh44(vpK6QFc-ao!yz(7`rl_2Pw#SIBXl}On4m%8+5 z>l?0u867eRG;&ld{yYySl#UEddhH#k!J2bCou3IFhfQ`3K ze1KgU?YsnQhiFl`iVt>ybmA#;!UYl_jMQ5x>8W;)=!YBEVa() z8v;^jX7GeLKVR$0^QZ>O{g((bdg`<%J;>2QC%BiAW-C+J5at(M@r385crijs=4oR_ z^0RV%97rPT)s0W6rM(f>=owe_j7A|;tbXtx9S^heHDMMaeIk+l?hUiMz+%y)NE>a= zc2dx!pwdap-R5xmEPWV3VtGn?Ka>f&4Zf31A=#fF`0^pDF13sS#e2S=vXyE#LA+GA zWCCwQ1?QMa2P8H2wUIcd*p7Bic$ke9G=*%pd-oAy#Z%9;FOmA$XS)Lo;(nMH?O-R^ zCel?zTxzqz<W};WJrz`& zLf=F7Wksx+1C156CuMb*`dcqj5=}7-=KKKyS^uD3RkIPi%XDWzPyF%M^$Bf^`R)4s ziArzDkKyHPbko*-!i6?7BwJrKx4YT$7vf!%w_vYeRBXE;7YmZi{e&zRZMHqIlZkm` zB2_jPJu$f#^8{rOyVFlAcn!kjl!O&z6h~7Vmi1R?JwF0CW$TkRM!O>I3Px6)8IQmU zWH^b9*GZKL)tXZ_w)l|iN!;{-WgXrWA#FfJ^7>=ttI2iObC}uhcJz@+D8iQ98ZoxY z#s2bUco}T_h5IKgq(1=RW745s!19d9pQ@W2j2Ha zAh|GQ)1#nbi4y%x{574=DSd2uS-6iWCqDf<$eR_@u9VeZko8jiQw-sf&F-ES)(03T zI5IR4P0)Or(Df*iZSuETEL9mW){I8JLWG^BGG!`o-YsIYQ*3{kyc9*-d5(j1^ zvFny5J%G}W&EQgJL1j(d+&hhrP^L3qmUn9cAg3_m73R_ z`JY-`LLs-D7q)eq#q272Cz`Xq6;zJfy_oKs=^94)V2(e7c_%k*5~)uj)5bB~n&7iU zdG>H*3^h3itmTLa!aj=-GS~9J{9bCyk0e0CdHg5;IO8-eK1xYaU4{O-*u z(4YLFrutVX^D=Dt*_`AVC$zX?C%A|6SLCY&07L@y%WoD#!f5Y}&)t z%cD6Ra2#>4hHU==?=ev9_C~8`{VS_8bF=(yRu7J0Mha9y5erp{v*|^hhu?>W=!<}0 zj_(npg*Pcm!Nj!bEku==S{b0n z$jM}_*@M@l)J@H9ES-zGbtGF-|1$IZ9Ru4r!AUL!jyi#dI@t=x5J9lt@awfyy47vH ztG--$rrR}`*R{mb8~%7hyD)S8{YihYA}?Hz5(<7TjLjD6GW@=m^iv;AE~*@{g&FY& zE9z2!9$!ev-1m7Uej7H}kynIxi z<_%Y{|1~wZx&O=zqW_%&S($&aHmWfxF|h&BBrxGZLg1)Fbc1z;76D{7F!9n8J5HC| z)5y?p4hLTT(uxMb3Hj4%3_c|Qb4a2!abnz3%9PO=apCOlj=YyH5HypfUss24cEIS* zMA|rbKsYa9;u}lm`0J9HnOOgJHvenM8cb}eOdJ|aENW8B+ceuO;IOziGT%3r{l@VUGZ)+6UbgKpKT@C}0@OMhR00EjKz3K`T0r<~5q67FLT?qu zAmQo-_eI8YIUw2*kplC4bIGWXd>(EqkL37rI6OE`==t(y+&+3W#jc?CV)uW+ zC+yd4qS}Pio0tPeIB=80i;Lk!j?oXGg0TT|&#{wdvdv>}DUOD{uD}1@BxXL{@dGcy zbqsnCY}D;qMo>z-7lrHwG(wO*3n!XtgL$T~b-DJy&Cyx5&AM_$ItDZL7}>uuIN;)X zBYa%{M)<&u(M2IuMT0R!t3((~;EC1%eUQ>*drsUQP6wg1*gCH%2N3@s!SnaV3bqi1 z4ANv!#0q`@fpD;2Kp;PWm^T1b5Zm9_4||@F2?Rt#011ov0OM+)Q7@tWAwWRvZX^l; z0PIJm3!oH}F){W_oMJ8xWK$519r?VITT4*aX}uu8`N}U$uuA~^5pj1Eh+vrWAL(#1 z81TkvBNz7@ZeZg2+v)JHEQU5Vf&Wi803lHzoP^PaTXhw(bssk12B35w2!xaweEw2< zi%Q)@e-vNds|N^QGZ+NyB@zIFz(TrXYFjh@tU#$!%{Nk+`z`6n@^6F?>c551`G1Bm z!})&?Au{$KA@rB??@tSe>Q?XuphXFW5&cn?pe;cfFto?6ox|aGLy&-j+>Z%=txE1U z&ij~OXIuY#{U7$f*K7p-XBeD`2*>_wGX5P)K*&r_ud9X*`wb*{VFmQRa5DKB{`Lb% z%?H7}Knn#v1EUO~&;|V=NcF)hKHmc)^lOw2MFR$c=)tvy5$ZB=wh=zY1^L$@%#s~~ zIGLFI5hO^ujCHHBbpvz(yPrQPykR}d8%fCh_ZwmIWFArv6AILNhLzLD*hw$2c^;ks z5*$*aKnRS$TtK+L|LH0Mfmtk5xTc$nEx0I3;Cns)U9zRBkD}>+y40orsxCsFv-+;>D zO{@-qjtV0R3#{KkO@3>NOlQM(k!$-+@N<3Ioe45*?G%JQFYGQOml?&79<=GgbwzW-Be~e=fESXB7I4o1Hx&X0RjZrL0C)385(E1{pgdGR>_hCsbM$vYJt1IHObDJm zFCaoH<=4VROZ~G_X)o0(-mv8@(s|AKznk8+WBhPELMV{yuwcL$_s?1AEwJH%KLZ5~ z;e=`7YgJ_7#dQ)h(hIg>s8#8BCoIS{kD(tw?!LRX;44ak^MTw!=P`5d*R5CUjM%EFk_Z0mHVn`0>P1f_G_*TJ&44kHi7kD5Ur@7#ThQ!MU8<_+`cX!yUoqH7LawFzM)NpZiFXQAk^YbCZKL%Zh^YD~G3? zlJJd2{fV~}&`@}5(~x)1+OZ^Ow0giot2X1ziE~8SR0`{bboB5UlW;FT;gfGyFDH4| z@4otjLU4s{{s!l4WhX#xpcN8O))_~8j_jTsl-QNp;7>B0CsgYZuHya1*vl zK=h06Bu%zpzAN~tE#OzX(;hKXZID^ft{wv7toTfv;U4+q^km8^O~13}8?JbZqPYIP zCWww<{v$KQI}1^YrL9xB12SrGYvB4|?EVSCmU$K#+ZF`mk~MOuEEC0(zk+?v%@+ji zKbBlU4rU%^_1mZlP`Z#>vm;LlW>zKfvUoM6jA@scE{71_?FGwq=sAfLuZ#9v#Orp$ z6oQdh2FQ3uiJ^wU4Dds2=%BI_FeVLI$;AmP?kM|CJ%_Kw<_VRH7~uZYuC`#?ACXCg z-eiZ$n<#J8SCMx`(&k>Ag06EFmwCerZ$TON-|u9}6SYWzLVrRTAGi;A06v0TDiOu1 zVSZweqlzSSbk%0YM!|2GD5O+g9S6smjgG?hhG%$oYT6O03tEh1?h|LeyaauUBJblE zO+2}|LK?Zf<^I`!Zhn=is+(@}huRpnsnqDts=8%#(SMdG!;D`x!9?$>`&z zz;zrSa2Jf=`n`6#8{ zb&i7N-Bhh7nU6@hXkP&k{Df+3r7&VI9kcp;mxb!Dom}j)+vuIz_&- zl@~bi8F_>l#n?Rd!Tf+U7Ld-n+?x1MpKe)}I9v2fm0#_W(zW-Vo5Bc0d!_IsaU}7C z+`Rd%C|fbL;P?HZEbRsS$_MS!LLI6)X#XDY$t6U>kEAU1yvso{0zFD%4Wg)FZbGQq zM94lp$zh)(?;uRtMRVZ_Ia@j=DJN;**<9mq8Fo1zT2XBspy0=hfiMItC{DX3U0|0h z!`d%k(su?gZ#d>HUSt0^x&oAu^M5u4ls}-A`9Bp$^eeCW=j%RXcfgU07nSKgD1c4C z33Wt@G~@*e)1{U~f&+mdWGE=8M?axWkRKG(00@MKohA(NnBz%w0VZ`WSOZPN3!uPu zK>wqA0Kb5-K??i#pIa9|8~wo+aXP+4sD{i88j4jy41mji{er(*%FOf@-o4%!^>>@b zzt9xI7~SMwwxa(JTjgGb)Z4bQDVhCYs>m}m6wz-GFslMOF$yF85?RRfnULuI3M5E$ z{|NFUx+fIzt+59rKyM4120eih4R;}vfdYmFrM8hh6K_C-fUMsj(BP|o;-tg4Qp+hd zh_C?YL7oq9S^O3Svi|+e@Lv|c!niD?D(Rn^MD%|GE#7Op|Eng+f9?4~^#5r22*(Hr z2$#@A^DkiFy?sKL?n2KXpwN^5h9JtEC^L}P$Km-^ue<^;2suR9`_C{w%_~yBWz?H+ z_xBY>))?>W-u4Q2#rHCO)e=Tv8Jtm(X(^*nH+Q zZ@He-6V5hLrY$PH&qIk4#8xt4gFUc+kaPXcu01dt^^?hs9_w9Vk{mJQduQwPZ|8CG z`{Ti&_;IYPz-&Ui?D$jkK@g?plOckxAqy~>2p6eGIyr14MoCly<5ss`m|`>F1kV}lT`QiE}-@#5iKaRX*`&$gh{%^$ZAG?zY>pyGtZ|+XYuj7NrakWXxochg9bSBSKc4?@L4?Hklq--`CO zT*dWHJ|yK;9i%CHis+#C832?5ieL%yQFzjV&o{X36BrJK8&QO~sq#!6$#cGO3T;vv z#kM>%@~<_{NcJ-5SM;fQSPT$_CpmPZ5DDWEL+k45EN>wTWGV5y<%c&gjpemX{oRgK z6#L(}9e)3#G)Xbq6PPNDwpLiOwl>w`7yR*86{EvIh4U%VKP6DryB}}2ozsy`N(D*F z*&vqd7e3;B?txsqhN!}(Tch9<8ENAN6?vlrPnpf|+~|pvRo$053ihqaT?fg9C};E} zq7QUKsx&B^al9B*6n?oF!r|b0LGN7XI&jzF>ARql2cIR?Cc3g3g_gMHaKQOzBvJ&v zS8c%MRflwtEnWb>j~WaWLEL@4m#CcLD`0O2Tdnpxe#CwZfcbS{1b)CYejV*cG*NJ#%Xk5+*Ae9`QSr^|;k4m63@$`gzyEaPJy4cn zZp>#m_`Hc!NxGaDLxxB_RjpV~f6I!nTS$s9kYExgzh5J0$F#2qO?D86c{zhtPaL38 z2A(_I0XB4RoZIcqy3Sk=cB7PUz|pt+t^&#lXbDyZ^9$d`-IJF8XRLsgA|Zd(f}SJR zifiZf@z;^PJCWuODLMTOzQJueM-w3GIkA3SZ)<0KGp=6Bjb^tu25rntZ<5>J?}L@Y ze<&Rch{+t8->K(0T`R5yeOAO04)Fh!z{283Hx}G7--Smox*bi37j?#h(xp!f*-+_w z(`LiokFK7l+hTq@wfb}%lj-%rx)!J0Ls#oQSeLOGSnkdJuJ>zPG-|t?yN5K8Y+6OMDi6K&OPKCW-f;^^1VjRO2UgnYZqIl1eG}{EM*7nmDZ%^} zy>b2h{ZW4OAHoiD5;d}mjeyu0oYRp#Za_$l=}(c1BHleRKIMcF8|M@mcL7aDx5`p~ z1)7a9FB41fm+z-=in$+f3*s9tJfMKPT3#o zR~fo@Dm|1$5rL{AxaUOlkp-TC8pNVWN#;<@v{g&r=V|1Dh)cq#NWhftL=Z%wjP442 zD)YI&W^o9Yf8|FeMIZY^aSbgU0?VuPrOBS|4yd>|6|08{%5B!QT?A(m+=}_oSu55_>KgbP1=&2Lz70RVpf*}I$0dagjvHkrd{};jje|N>PS3&aH0AHu-!4;rx z1Tq{yrgLN}>R7Qw;IJUV4fN~0Heh++oDc>Z`!K*17{s5J1o9)0VgZ@Fw$DGYr?0?N z;4LHHLbrcsSo{BLBnZK9_8cVG>xZ%cFcR%sKxSg{B@$v0>=JV`tRqlh@hyd>eOZek z>LeYCU5h0&Gz26dA|MD7=7c785_U?GmLqqV8yc1h{w5UWAd-3v@e@+X&I<|nk8`i# zEZh9I%zuOWSpE+WD2Y$hTJ`=htXMOJ2@53b_6)c^o~Is16a6B>4>4UPI-F!WHaTrK z;5IZlXLS$>!Q(QHiA;et%#R=Qcp7&~Mt(8w&_|5?o2PBdwCjStn#8k6vw#>GJyqMz zA|c*m*IjH^dDFBGr?bHSuW$S^N% zk(=)DcPTTcB;Fm>2aFChsYn(05^XLn*bd(lS+wi8DfBF`wQ(b_H@wI4S8ik`VrJ)N z`j1ERScsT8m{|Yy^)=W2Gb%FhQyBF z^JUSE)9bc?<)`eXFr?@k5Kx3LgaEeubO1>MYJTJznjHJWUsVgQAIc-cay=C8^A;b~y* zxYUoKp`nrC;jv)|!>6MwgNjG>w($0>=)JW(Cj)u_Q`pexSLuk!r?Xqoprq#fDsWwX zuy1SvHrK@56oSc_iN)t8IT3#Sv6GbsTl-aba3r+>% z5vJ(^O^}(N8C%-Gfdz4)TY1O%4HeWNbE7!O%Hv!Uc7P&HB zto43Q^vwe`fXr( zc6g2aIQMH8B=i9g)Gru^*kyQ+{G{^n8Tkp#XFlT1swqtH+)?*`XV^=uw!k1 zhnyv6FoOTW;m1DRJv%)8LKw{KBmEfaP|RpK0qUxTN$os(S{&=|8NNP4obQ-zX;}i1 zu9suZ`K@cv+kv_meNO#IuQVD41JcCvV+^E_LZgV^f^suUCqklR5X zD&FH)oeN`^M^()1H%#myT6%SyyRfxZFbCdW(R*A=c+)o5@dQ7tPjs)OEz34fJeYdk zpAgr7!O9qtB}gMTo2AA_&?J8!3Ti5lG#LAa`(r$f$v{oeR1wQO`|#H3F=R_RCSK^^ zD2qgUt;O7SFQAPwxZAi7a>L9p#1*Qzq6x+S>sfCF@%4NmPFQ1l?6ppfz5prs*=M#? zxj@9@fc?Rp!PYnmbAATlZk|!HMG0hMw1cB%nK>#8i!VXMZ1%kK>H;TW#85I?q;h?e z?K~n{kqYB^#t(JxeNO88M@x`d+=WH5gmd*Z+mdeNSndvGaQW3!G{q)(#gx5CJ1oH2 zqZ7&nL~FIMTta{T$!S~TZ9RQ&8N7wmB5N8uTH zDPeF-aEAe>doh8Oi>T;M+74{UwI6+58AdpuDl;Z2Q5ch2Blor3*ZW05$QEI!T!;45%SHDI}4v@MQ(BEu4m|RGS+DQ-D4-*)drBKrcheV8b%c# zS^uYUP^GN&;R1_2ct^l<8ebgfJ~MB<>D{6)bP8C_7Hoy=qKA+6ih$t9n$+n$*ePy# z^%JIAs1+oW`qAZ88AF72rkV;&B*=h$e2*KmS{R1+>6Nj%yu&%LweCykXD|LOS=Ok^ zS82GTR{hH_;;>5cdn8qgK1RW;6wO||0;W+u0G^MNb~}TYzQxNspI+P|M5i>hw^M)8 z5Rh(ulhyci5n&6pF@lgL-T2NmvA>6dj<@l<>O+cHsJWs-J&cb>Tcu9k$z@ikC-M&^ z_@)ai7^KhR0wfF#;$)`95Hsa79mnhFX#HzL5j>gE|hAU4Ls!j z_PwxAF?mpE##Nkug{Y1}S)r|D4R$u2@$ZIFj?;>@M!9KGL4&B9J6lQ!h%jnqOQ-TL ztdL`_qow81Gw5*uk44uXSyWPu>tv&lxnYo70&Rlm?6 z)zc{$qZRS?ux?OzCfnmG$?cG5r@3Maroa2*drfpx=9G;pm_Z=IRycKV6kOrm+eQ`$ zrl@6$gklYrO|!i7SX5s2LxnE+K-nVsvVrx!mTuU1r8vDbq6g^${=&Qa&a+p$AN#|t zcpO#vZ}PS0d@TB3ZJ#i1-932+gw!8rliufhtfQI-{>&yMXMwDR9ceToZlt9ad>1|6TQdi}rGpH&y*_2bl9riCKcL?L-Uhhi$iY5_} zOd3oXxKSmtIFj^eA^>vFu$WsInR)pWP$w^ELJY_D}rRa_65mG zUzEbqBAhA4{)d>`w^0sXsFoOf|+A?J)APjtoSCqk2O%OsroAx>D4 zt3HV_G>RD#lVZNfBbC0@P7_JKChQL3geW(VGiXP!H?38S63)09qCtu$PUymih=%tWae&moXmSu^0`U~@XBA=H0%##M zKctCz@397I(UU#*Bm$Q!W6gI%b44g^{a7Jx`Et0_G@~;(Oj}rAW`Fm{kT@s7Wtrip zY<)mG&O~NAw}?yyWnqHy6w~bb=l5U5kF>%q~i5C={@(i8Cf@k zhwv#+O6a*ooDmp>r^}oHggUB&0?~%x8HiY@5#VarqDw(N=c2zHtZ7AVvAW2(EH67g z-`7z9hildxfKhb3CopKtR~AKE*WG=yEzZfIlUMv1@ukC3+N+SjmtX#UcLk0Cu$?%_GkI6lAa`aM&d<;Hi-YK;B0curva zIMY-6%h0w$ug;S6)#nyx>6thVuJJXq-iXkpjYF(b?QZ10sGR1_aSfSBm#PH%z`
`xon`{G6^22;7ujPNyg>2v1#@IP)&s$0o#?fOf> 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
wzhOhy^2(%`)gmjI&gK?Ba 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 literal 0 HcmV?d00001 From 3a33a0c2e46ef3d98f67a0fd4bd6a2cdabbc2dcd Mon Sep 17 00:00:00 2001 From: Jiatu Date: Thu, 19 Dec 2024 10:32:14 +0800 Subject: [PATCH 2/4] Add `makePaymentOnBehalfOf()` --- contracts/common/Errors.sol | 1 + contracts/credit/Credit.sol | 4 + contracts/credit/CreditLine.sol | 14 + .../credit/ReceivableBackedCreditLine.sol | 35 ++ contracts/credit/interfaces/ICreditLine.sol | 17 + contracts/liquidity/TrancheVault.sol | 1 + scripts/error-functions.json | 1 + test/unit/credit/CreditLineTest.ts | 487 ++++++++++++++++++ .../credit/ReceivableBackedCreditLineTest.ts | 341 ++++++++++++ 9 files changed, 901 insertions(+) diff --git a/contracts/common/Errors.sol b/contracts/common/Errors.sol index 9c95ef29..e5f2e295 100644 --- a/contracts/common/Errors.sol +++ b/contracts/common/Errors.sol @@ -16,6 +16,7 @@ contract Errors { error PoolOwnerOrHumaOwnerRequired(); // 0x3e984120 error PoolOperatorRequired(); // 0xae7fe070 error PoolOwnerRequired(); // 0x8b506451 + error PoolOwnerTreasuryRequired(); // 0xf527eb38 error HumaTreasuryRequired(); // 0x6e0a9ac9 error PoolOwnerOrEARequired(); // 0xe54466f3 error PauserRequired(); // 0xd4a99e4e diff --git a/contracts/credit/Credit.sol b/contracts/credit/Credit.sol index bc8b439b..5c0a4601 100644 --- a/contracts/credit/Credit.sol +++ b/contracts/credit/Credit.sol @@ -632,6 +632,10 @@ abstract contract Credit is PoolConfigCache, CreditStorage, ICredit { revert Errors.SentinelServiceAccountRequired(); } + function _onlyPoolOwnerTreasury(address account) internal view { + if (account != poolConfig.poolOwnerTreasury()) revert Errors.PoolOwnerTreasuryRequired(); + } + /** * @notice Returns from whose account the funds for payment should be extracted. * @notice This function exists because of Auto-pay: diff --git a/contracts/credit/CreditLine.sol b/contracts/credit/CreditLine.sol index 4628d684..b1c05901 100644 --- a/contracts/credit/CreditLine.sol +++ b/contracts/credit/CreditLine.sol @@ -36,6 +36,20 @@ contract CreditLine is Credit, ICreditLine { return _makePayment(borrower, creditHash, amount); } + /// @inheritdoc ICreditLine + function makePaymentOnBehalfOf( + address borrower, + uint256 amount + ) external virtual override returns (uint256 amountPaid, bool paidoff) { + poolConfig.onlyProtocolAndPoolOn(); + _onlyPoolOwnerTreasury(msg.sender); + + bytes32 creditHash = getCreditHash(borrower); + creditManager.onlyCreditBorrower(creditHash, borrower); + + return _makePayment(borrower, creditHash, amount); + } + /// @inheritdoc ICreditLine function makePrincipalPayment( uint256 amount diff --git a/contracts/credit/ReceivableBackedCreditLine.sol b/contracts/credit/ReceivableBackedCreditLine.sol index efa38d18..cc6a94d3 100644 --- a/contracts/credit/ReceivableBackedCreditLine.sol +++ b/contracts/credit/ReceivableBackedCreditLine.sol @@ -37,6 +37,20 @@ contract ReceivableBackedCreditLine is Credit, IERC721Receiver { address by ); + /** + * @notice A payment has been made against the credit line by someone on behalf of the borrower. + * @param borrower The address of the borrower. + * @param receivableId The ID of the receivable. + * @param amount The payback amount. + * @param by The address that initiated the payment. + */ + event PaymentMadeOnBehalfOfWithReceivable( + address indexed borrower, + uint256 indexed receivableId, + uint256 amount, + address by + ); + /** * @notice A borrowing event has happened to the credit line. * @param borrower The address of the borrower. @@ -118,6 +132,27 @@ contract ReceivableBackedCreditLine is Credit, IERC721Receiver { emit PaymentMadeWithReceivable(borrower, receivableId, amount, msg.sender); } + /** + * @notice Allows the Pool Owner Treasury to pay back on behalf of the borrower with a receivable + */ + function makePaymentOnBehalfOfWithReceivable( + address borrower, + uint256 receivableId, + uint256 amount + ) public virtual returns (uint256 amountPaid, bool paidoff) { + poolConfig.onlyProtocolAndPoolOn(); + _onlyPoolOwnerTreasury(msg.sender); + + bytes32 creditHash = getCreditHash(borrower); + creditManager.onlyCreditBorrower(creditHash, borrower); + + _prepareForPayment(borrower, poolConfig.receivableAsset(), receivableId); + + (amountPaid, paidoff) = _makePayment(borrower, creditHash, amount); + + emit PaymentMadeOnBehalfOfWithReceivable(borrower, receivableId, amount, msg.sender); + } + /** * @notice Allows the borrower to payback the principal and label it with a receivable */ diff --git a/contracts/credit/interfaces/ICreditLine.sol b/contracts/credit/interfaces/ICreditLine.sol index 784eda8f..b0fdc995 100644 --- a/contracts/credit/interfaces/ICreditLine.sol +++ b/contracts/credit/interfaces/ICreditLine.sol @@ -30,6 +30,23 @@ interface ICreditLine { uint256 amount ) external returns (uint256 amountPaid, bool paidoff); + /** + * @notice Makes one payment for the credit line by the pool owner treasury on behalf of the borrower. + * If this is the final payment, it automatically triggers the payoff process. + * @notice Warning: payments should be made by calling this function. No token should be transferred directly + * to the contract. + * @param borrower The address of the borrower. + * @param amount The payment amount. + * @return amountPaid The actual amount paid to the contract. When the tendered + * amount is larger than the payoff amount, the contract only accepts the payoff amount. + * @return paidoff A flag indicating whether the account has been paid off. + * @custom:access Only the Pool Owner Treasury can call this function. + */ + function makePaymentOnBehalfOf( + address borrower, + uint256 amount + ) external returns (uint256 amountPaid, bool paidoff); + /** * @notice Makes a payment towards the principal for the credit line. Even if there is additional amount remaining * after the principal is paid off, this function will only accept the amount up to the total principal due. diff --git a/contracts/liquidity/TrancheVault.sol b/contracts/liquidity/TrancheVault.sol index 975dfa85..5b08b9b9 100644 --- a/contracts/liquidity/TrancheVault.sol +++ b/contracts/liquidity/TrancheVault.sol @@ -506,6 +506,7 @@ contract TrancheVault is DepositRecord memory depositRecord = _getDepositRecord(lender); uint256 principalWithDecimals = depositRecord.principal * DEFAULT_DECIMALS_FACTOR; if (assetsWithDecimals > principalWithDecimals) { + // TODO(jiatu): Check yield > 0 instead? uint256 yieldWithDecimals = assetsWithDecimals - principalWithDecimals; uint256 yield = yieldWithDecimals / DEFAULT_DECIMALS_FACTOR; // Round up the number of shares the lender has to burn in order to receive diff --git a/scripts/error-functions.json b/scripts/error-functions.json index 2818f29a..a78f9ad5 100644 --- a/scripts/error-functions.json +++ b/scripts/error-functions.json @@ -10,6 +10,7 @@ "PoolOwnerOrHumaOwnerRequired()": "0x3e984120", "PoolOperatorRequired()": "0xae7fe070", "PoolOwnerRequired()": "0x8b506451", + "PoolOwnerTreasuryRequired()": "0xf527eb38", "HumaTreasuryRequired()": "0x6e0a9ac9", "PoolOwnerOrEARequired()": "0xe54466f3", "PauserRequired()": "0xd4a99e4e", diff --git a/test/unit/credit/CreditLineTest.ts b/test/unit/credit/CreditLineTest.ts index 0ff73bfa..2a1c4c0e 100644 --- a/test/unit/credit/CreditLineTest.ts +++ b/test/unit/credit/CreditLineTest.ts @@ -7422,6 +7422,493 @@ describe("CreditLine Test", function () { }); }); + describe("makePaymentOnBehalfOf", function () { + const yieldInBps = 1217, + lateFeeBps = 300, + latePaymentGracePeriodInDays = 5, + remainingPeriods = 6; + let principalRateInBps: number; + let borrowAmount: BN, creditHash: string; + let drawdownDate: moment.Moment, makePaymentDate: moment.Moment; + + async function approveCredit() { + const settings = await poolConfigContract.getPoolSettings(); + await poolConfigContract + .connect(poolOwner) + .setPoolSettings({ ...settings, ...{ latePaymentGracePeriodInDays: 5 } }); + + await creditManagerContract + .connect(evaluationAgent) + .approveBorrower( + borrower.getAddress(), + toToken(100_000), + remainingPeriods, + yieldInBps, + toToken(100_000), + 0, + true, + ); + } + + async function drawdown() { + const currentTS = (await getLatestBlock()).timestamp; + drawdownDate = moment.utc( + ( + await calendarContract.getStartDateOfNextPeriod( + PayPeriodDuration.Monthly, + currentTS, + ) + ).toNumber() * 1000, + ); + drawdownDate = drawdownDate + .add(11, "days") + .add(13, "hours") + .add(47, "minutes") + .add(8, "seconds"); + await setNextBlockTimestamp(drawdownDate.unix()); + + borrowAmount = toToken(50_000); + await creditContract.connect(borrower).drawdown(borrowAmount); + } + + async function testMakePaymentOnBehalfOf( + paymentAmount: BN, + paymentDate: moment.Moment = makePaymentDate, + paymentInitiator: SignerWithAddress = borrower, + ) { + const cc = await creditManagerContract.getCreditConfig(creditHash); + const cr = await creditContract.getCreditRecord(creditHash); + const dd = await creditContract.getDueDetail(creditHash); + const maturityDate = moment.utc( + getMaturityDate(cc.periodDuration, cc.numOfPeriods - 1, drawdownDate.unix()) * + 1000, + ); + + // Calculate the dues, fees and dates right before the payment is made. + let [ + remainingUnbilledPrincipal, + remainingPrincipalPastDue, + remainingPrincipalNextDue, + ] = await calcPrincipalDueNew( + calendarContract, + cc, + cr, + dd, + paymentDate, + maturityDate, + latePaymentGracePeriodInDays, + principalRateInBps, + ); + let [ + remainingYieldPastDue, + remainingYieldNextDue, + [accruedYieldNextDue, committedYieldNextDue], + ] = await calcYieldDueNew( + calendarContract, + cc, + cr, + dd, + paymentDate, + latePaymentGracePeriodInDays, + ); + let [lateFeeUpdatedDate, remainingLateFee] = await calcLateFeeNew( + poolConfigContract, + calendarContract, + cc, + cr, + dd, + paymentDate, + latePaymentGracePeriodInDays, + ); + let nextDueBefore = remainingPrincipalNextDue.add(remainingYieldNextDue); + + let principalDuePaid = BN.from(0), + yieldDuePaid = BN.from(0), + unbilledPrincipalPaid = BN.from(0), + principalPastDuePaid = BN.from(0), + yieldPastDuePaid = BN.from(0), + lateFeePaid = BN.from(0), + remainingPaymentAmount = paymentAmount; + // If there is past due, attempt to pay past due first. + let remainingPastDue = remainingPrincipalPastDue + .add(remainingYieldPastDue) + .add(remainingLateFee); + if (remainingPastDue.gt(0)) { + if (paymentAmount.gte(remainingPastDue)) { + yieldPastDuePaid = remainingYieldPastDue; + remainingYieldPastDue = BN.from(0); + principalPastDuePaid = remainingPrincipalPastDue; + remainingPrincipalPastDue = BN.from(0); + lateFeePaid = remainingLateFee; + remainingLateFee = BN.from(0); + remainingPaymentAmount = paymentAmount.sub(remainingPastDue); + } else if (paymentAmount.gte(remainingYieldPastDue.add(remainingLateFee))) { + principalPastDuePaid = paymentAmount + .sub(remainingYieldPastDue) + .sub(remainingLateFee); + remainingPrincipalPastDue = + remainingPrincipalPastDue.sub(principalPastDuePaid); + yieldPastDuePaid = remainingYieldPastDue; + remainingYieldPastDue = BN.from(0); + lateFeePaid = remainingLateFee; + remainingLateFee = BN.from(0); + remainingPaymentAmount = BN.from(0); + } else if (paymentAmount.gte(remainingYieldPastDue)) { + lateFeePaid = paymentAmount.sub(remainingYieldPastDue); + remainingLateFee = remainingLateFee.sub(lateFeePaid); + yieldPastDuePaid = remainingYieldPastDue; + remainingYieldPastDue = BN.from(0); + remainingPaymentAmount = BN.from(0); + } else { + yieldPastDuePaid = paymentAmount; + remainingYieldPastDue = remainingYieldPastDue.sub(paymentAmount); + remainingPaymentAmount = BN.from(0); + } + remainingPastDue = remainingPrincipalPastDue + .add(remainingYieldPastDue) + .add(remainingLateFee); + } + // Then pay next due. + let nextDueAfter = nextDueBefore; + if (remainingPaymentAmount.gt(0)) { + if (remainingPaymentAmount.gte(nextDueBefore)) { + yieldDuePaid = remainingYieldNextDue; + principalDuePaid = remainingPrincipalNextDue; + remainingPaymentAmount = remainingPaymentAmount.sub(nextDueBefore); + remainingYieldNextDue = BN.from(0); + remainingPrincipalNextDue = BN.from(0); + unbilledPrincipalPaid = minBigNumber( + remainingUnbilledPrincipal, + remainingPaymentAmount, + ); + remainingUnbilledPrincipal = + remainingUnbilledPrincipal.sub(unbilledPrincipalPaid); + remainingPaymentAmount = remainingPaymentAmount.sub(unbilledPrincipalPaid); + } else if (remainingPaymentAmount.gte(remainingYieldNextDue)) { + yieldDuePaid = remainingYieldNextDue; + principalDuePaid = remainingPaymentAmount.sub(remainingYieldNextDue); + remainingPrincipalNextDue = remainingPrincipalNextDue.sub( + remainingPaymentAmount.sub(remainingYieldNextDue), + ); + remainingYieldNextDue = BN.from(0); + remainingPaymentAmount = BN.from(0); + } else { + yieldDuePaid = remainingPaymentAmount; + remainingYieldNextDue = remainingYieldNextDue.sub(remainingPaymentAmount); + remainingPaymentAmount = BN.from(0); + } + nextDueAfter = remainingYieldNextDue.add(remainingPrincipalNextDue); + } + + // Clear late fee updated date if the bill is paid off + if (remainingPastDue.isZero()) { + lateFeeUpdatedDate = BN.from(0); + } + + let newDueDate; + if ( + paymentDate.isSameOrBefore( + getNextBillRefreshDate(cr, paymentDate, latePaymentGracePeriodInDays), + ) + ) { + newDueDate = cr.nextDueDate; + } else { + newDueDate = await calendarContract.getStartDateOfNextPeriod( + cc.periodDuration, + paymentDate.unix(), + ); + } + const paymentAmountUsed = paymentAmount.sub(remainingPaymentAmount); + + const oldPoolOwnerTreasuryBalance = await mockTokenContract.balanceOf( + poolOwnerTreasury.getAddress(), + ); + + if (paymentAmountUsed.gt(ethers.constants.Zero)) { + let poolDistributionEventName = ""; + if (cr.state === CreditState.Defaulted) { + poolDistributionEventName = "LossRecoveryDistributed"; + } else if (yieldPastDuePaid.add(yieldDuePaid).add(lateFeePaid).gt(0)) { + poolDistributionEventName = "ProfitDistributed"; + } + + if (poolDistributionEventName !== "") { + await expect( + creditContract + .connect(paymentInitiator) + .makePaymentOnBehalfOf(borrower.getAddress(), paymentAmount), + ) + .to.emit(creditContract, "PaymentMade") + .withArgs( + await borrower.getAddress(), + await poolOwnerTreasury.getAddress(), + paymentAmountUsed, + yieldDuePaid, + principalDuePaid, + unbilledPrincipalPaid, + yieldPastDuePaid, + lateFeePaid, + principalPastDuePaid, + await paymentInitiator.getAddress(), + ) + .to.emit(poolContract, poolDistributionEventName); + } else { + await expect( + creditContract + .connect(paymentInitiator) + .makePaymentOnBehalfOf(borrower.getAddress(), paymentAmount), + ) + .to.emit(creditContract, "PaymentMade") + .withArgs( + await borrower.getAddress(), + await poolOwnerTreasury.getAddress(), + paymentAmountUsed, + yieldDuePaid, + principalDuePaid, + unbilledPrincipalPaid, + yieldPastDuePaid, + lateFeePaid, + principalPastDuePaid, + await paymentInitiator.getAddress(), + ); + } + } else { + await expect( + creditContract + .connect(paymentInitiator) + .makePaymentOnBehalfOf(borrower.getAddress(), paymentAmount), + ).not.to.emit(creditContract, "PaymentMade"); + } + + const newPoolOwnerTreasuryBalance = await mockTokenContract.balanceOf( + poolOwnerTreasury.getAddress(), + ); + expect(oldPoolOwnerTreasuryBalance.sub(newPoolOwnerTreasuryBalance)).to.equal( + paymentAmountUsed, + ); + + const newCR = await creditContract.getCreditRecord(creditHash); + let periodsPassed = 0; + if (cr.state === CreditState.Approved) { + periodsPassed = 1; + } else if (cr.state === CreditState.GoodStanding) { + if ( + paymentDate.isAfter( + getLatePaymentGracePeriodDeadline(cr, latePaymentGracePeriodInDays), + ) + ) { + periodsPassed = + ( + await calendarContract.getNumPeriodsPassed( + cc.periodDuration, + cr.nextDueDate, + paymentDate.unix(), + ) + ).toNumber() + 1; + } + } else if (paymentDate.isAfter(moment.utc(cr.nextDueDate.toNumber() * 1000))) { + periodsPassed = + ( + await calendarContract.getNumPeriodsPassed( + cc.periodDuration, + cr.nextDueDate, + paymentDate.unix(), + ) + ).toNumber() + 1; + } + const remainingPeriods = Math.max(cr.remainingPeriods - periodsPassed, 0); + // Whether the bill is late up until payment is made. + const isLate = + cr.missedPeriods > 0 || + (cr.nextDue.gt(0) && + paymentDate.isAfter( + getLatePaymentGracePeriodDeadline(cr, latePaymentGracePeriodInDays), + )); + const missedPeriods = + !isLate || remainingPastDue.isZero() ? 0 : cr.missedPeriods + periodsPassed; + let creditState; + if (remainingPastDue.isZero()) { + if (nextDueAfter.isZero() && remainingUnbilledPrincipal.isZero()) { + if (remainingPeriods === 0) { + creditState = CreditState.Deleted; + } else { + creditState = CreditState.GoodStanding; + } + } else if (cr.state === CreditState.Delayed) { + creditState = CreditState.GoodStanding; + } else { + creditState = cr.state; + } + } else if (missedPeriods != 0) { + if (cr.state === CreditState.GoodStanding) { + creditState = CreditState.Delayed; + } else { + creditState = cr.state; + } + } else { + creditState = cr.state; + } + let expectedNewCR, expectedNewDD; + if ( + nextDueAfter.isZero() && + !remainingUnbilledPrincipal.isZero() && + newDueDate.lt(paymentDate.unix()) + ) { + // We expect the bill to be refreshed if all next due is paid off and the bill is in the + // new billing cycle. + const [accrued, committed] = calcYieldDue( + cc, + remainingUnbilledPrincipal, + CONSTANTS.DAYS_IN_A_MONTH, + ); + const yieldDue = maxBigNumber(accrued, committed); + let principalDue; + newDueDate = await calendarContract.getStartDateOfNextPeriod( + cc.periodDuration, + newDueDate, + ); + if (newDueDate.eq(maturityDate.unix())) { + principalDue = remainingUnbilledPrincipal; + } else { + principalDue = calcPrincipalDueForFullPeriods( + remainingUnbilledPrincipal, + principalRateInBps, + 1, + ); + } + expectedNewCR = { + unbilledPrincipal: remainingUnbilledPrincipal.sub(principalDue), + nextDueDate: newDueDate, + nextDue: yieldDue.add(principalDue), + yieldDue: yieldDue, + totalPastDue: BN.from(0), + missedPeriods: 0, + remainingPeriods: remainingPeriods - 1, + state: CreditState.GoodStanding, + }; + expectedNewDD = { + lateFeeUpdatedDate: 0, + lateFee: 0, + principalPastDue: 0, + yieldPastDue: 0, + accrued: accrued, + committed: committed, + paid: 0, + }; + } else { + expectedNewCR = { + unbilledPrincipal: remainingUnbilledPrincipal, + nextDueDate: newDueDate, + nextDue: nextDueAfter, + yieldDue: remainingYieldNextDue, + totalPastDue: remainingPastDue, + missedPeriods, + remainingPeriods, + state: creditState, + }; + const yieldPaidInCurrentCycle = + newDueDate === cr.nextDueDate ? dd.paid.add(yieldDuePaid) : yieldDuePaid; + expectedNewDD = { + lateFeeUpdatedDate, + lateFee: remainingLateFee, + principalPastDue: remainingPrincipalPastDue, + yieldPastDue: remainingYieldPastDue, + accrued: accruedYieldNextDue, + committed: committedYieldNextDue, + paid: yieldPaidInCurrentCycle, + }; + } + await checkCreditRecordsMatch(newCR, expectedNewCR); + + const newDD = await creditContract.getDueDetail(creditHash); + await checkDueDetailsMatch(newDD, expectedNewDD); + } + + async function prepare() { + creditHash = await borrowerLevelCreditHash(creditContract, borrower); + + principalRateInBps = 0; + await poolConfigContract.connect(poolOwner).setFeeStructure({ + yieldInBps, + minPrincipalRateInBps: principalRateInBps, + lateFeeBps, + }); + await approveCredit(); + await drawdown(); + + makePaymentDate = drawdownDate + .clone() + .add(16, "days") + .add(2, "hours") + .add(31, "seconds"); + await setNextBlockTimestamp(makePaymentDate.unix()); + } + + beforeEach(async function () { + await loadFixture(prepare); + }); + + it("Should allow the borrower to make multiple payments", async function () { + const cc = await creditManagerContract.getCreditConfig(creditHash); + const cr = await creditContract.getCreditRecord(creditHash); + const dd = await creditContract.getDueDetail(creditHash); + + const [, yieldNextDue] = await calcYieldDueNew( + calendarContract, + cc, + cr, + dd, + makePaymentDate, + latePaymentGracePeriodInDays, + ); + + // Make a series of payment gradually and eventually pay off the bill. + await testMakePaymentOnBehalfOf(yieldNextDue, makePaymentDate, poolOwnerTreasury); + + const secondPaymentDate = makePaymentDate.clone().add(1, "day").add(4, "hours"); + setNextBlockTimestamp(secondPaymentDate.unix()); + await testMakePaymentOnBehalfOf(borrowAmount, secondPaymentDate, poolOwnerTreasury); + + const thirdPaymentDate = secondPaymentDate.clone().add("3", "hours"); + setNextBlockTimestamp(thirdPaymentDate.unix()); + await testMakePaymentOnBehalfOf(toToken(1), thirdPaymentDate, poolOwnerTreasury); + }); + + it("Should not allow payment when the protocol is paused or the pool is not on", async function () { + await humaConfigContract.connect(protocolOwner).pause(); + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOf(borrower.getAddress(), toToken(1)), + ).to.be.revertedWithCustomError(poolConfigContract, "ProtocolIsPaused"); + await humaConfigContract.connect(protocolOwner).unpause(); + + await poolContract.connect(poolOwner).disablePool(); + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOf(borrower.getAddress(), toToken(1)), + ).to.be.revertedWithCustomError(poolConfigContract, "PoolIsNotOn"); + await poolContract.connect(poolOwner).enablePool(); + }); + + it("Should not allow non-Pool-Owner-Treasury to make payment on behalf of the borrower", async function () { + await expect( + creditContract + .connect(borrower) + .makePaymentOnBehalfOf(borrower.getAddress(), toToken(1)), + ).to.be.revertedWithCustomError(creditContract, "PoolOwnerTreasuryRequired"); + }); + + it("Should not allow payment with 0 amount", async function () { + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOf(borrower.getAddress(), 0), + ).to.be.revertedWithCustomError(creditContract, "ZeroAmountProvided"); + }); + }); + describe("makePrincipalPayment", function () { const yieldInBps = 1217, lateFeeBps = 300, diff --git a/test/unit/credit/ReceivableBackedCreditLineTest.ts b/test/unit/credit/ReceivableBackedCreditLineTest.ts index 3223303e..f1be1eb8 100644 --- a/test/unit/credit/ReceivableBackedCreditLineTest.ts +++ b/test/unit/credit/ReceivableBackedCreditLineTest.ts @@ -929,6 +929,347 @@ describe("ReceivableBackedCreditLine Tests", function () { }); }); + describe("makePaymentOnBehalfOfWithReceivable", function () { + const yieldInBps = 1217, + principalRate = 100, + lateFeeBps = 2400; + const numOfPeriods = 3, + latePaymentGracePeriodInDays = 5; + let maturityDate: number; + let committedAmount: BN, borrowAmount: BN, receivableId: BN; + let creditHash: string; + + async function prepareForMakePayment() { + const settings = await poolConfigContract.getPoolSettings(); + await poolConfigContract.connect(poolOwner).setPoolSettings({ + ...settings, + ...{ + latePaymentGracePeriodInDays: latePaymentGracePeriodInDays, + advanceRateInBps: CONSTANTS.BP_FACTOR, + receivableAutoApproval: true, + }, + }); + await poolConfigContract.connect(poolOwner).setFeeStructure({ + yieldInBps, + minPrincipalRateInBps: principalRate, + lateFeeBps, + }); + + committedAmount = toToken(10_000); + borrowAmount = toToken(15_000); + creditHash = await borrowerLevelCreditHash(creditContract, borrower); + + const currentTS = (await getLatestBlock()).timestamp; + maturityDate = ( + await calendarContract.getStartDateOfNextPeriod( + PayPeriodDuration.Monthly, + currentTS, + ) + ).toNumber(); + await receivableContract + .connect(borrower) + .createReceivable(1, borrowAmount, maturityDate, "", ""); + receivableId = await receivableContract.tokenOfOwnerByIndex(borrower.getAddress(), 0); + await receivableContract + .connect(borrower) + .approve(creditContract.address, receivableId); + } + + async function approveAndDrawdown() { + await creditManagerContract + .connect(evaluationAgent) + .approveBorrower( + borrower.getAddress(), + toToken(100_000), + numOfPeriods, + yieldInBps, + committedAmount, + 0, + true, + ); + await creditContract + .connect(borrower) + .drawdownWithReceivable(receivableId, borrowAmount); + } + + beforeEach(async function () { + await loadFixture(prepareForMakePayment); + }); + + describe("Without credit approval", function () { + it("Should not allow payment on a non-existent credit", async function () { + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + borrowAmount, + ), + ).to.be.revertedWithCustomError(creditManagerContract, "BorrowerRequired"); + }); + }); + + describe("With credit approval", function () { + beforeEach(async function () { + await loadFixture(approveAndDrawdown); + }); + + it("Should allow the Pool Owner Treasury to make payment on behalf of the borrower", async function () { + const oldCR = await creditContract.getCreditRecord(creditHash); + const oldDD = await creditContract.getDueDetail(creditHash); + const paymentAmount = oldCR.yieldDue; + + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + paymentAmount, + ), + ) + .to.emit(creditContract, "PaymentMade") + .withArgs( + await borrower.getAddress(), + await poolOwnerTreasury.getAddress(), + paymentAmount, + oldCR.yieldDue, + 0, + 0, + 0, + 0, + 0, + await poolOwnerTreasury.getAddress(), + ) + .to.emit(creditContract, "PaymentMadeOnBehalfOfWithReceivable") + .withArgs( + await borrower.getAddress(), + receivableId, + paymentAmount, + await poolOwnerTreasury.getAddress(), + ); + + const actualCR = await creditContract.getCreditRecord(creditHash); + const expectedCR = { + ...oldCR, + ...{ + nextDue: oldCR.nextDue.sub(oldCR.yieldDue), + yieldDue: 0, + }, + }; + checkCreditRecordsMatch(actualCR, expectedCR); + + const actualDD = await creditContract.getDueDetail(creditHash); + const expectedDD = { + ...oldDD, + ...{ + paid: paymentAmount, + }, + }; + checkDueDetailsMatch(actualDD, expectedDD); + }); + + it("Should allow the Pool Owner Treasury to make payment even if the receivable has matured", async function () { + const oldCR = await creditContract.getCreditRecord(creditHash); + const oldDD = await creditContract.getDueDetail(creditHash); + const paymentAmount = oldCR.yieldDue; + + // Advance the block timestamp to be after the maturity date and make sure the payment can + // still go through. + await setNextBlockTimestamp(maturityDate + 1); + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + paymentAmount, + ), + ) + .to.emit(creditContract, "PaymentMade") + .withArgs( + await borrower.getAddress(), + await poolOwnerTreasury.getAddress(), + paymentAmount, + oldCR.yieldDue, + 0, + 0, + 0, + 0, + 0, + await poolOwnerTreasury.getAddress(), + ) + .to.emit(creditContract, "PaymentMadeOnBehalfOfWithReceivable") + .withArgs( + await borrower.getAddress(), + receivableId, + paymentAmount, + await poolOwnerTreasury.getAddress(), + ); + + const actualCR = await creditContract.getCreditRecord(creditHash); + const expectedCR = { + ...oldCR, + ...{ + nextDue: oldCR.nextDue.sub(oldCR.yieldDue), + yieldDue: 0, + }, + }; + checkCreditRecordsMatch(actualCR, expectedCR); + + const actualDD = await creditContract.getDueDetail(creditHash); + const expectedDD = { + ...oldDD, + ...{ + paid: paymentAmount, + }, + }; + checkDueDetailsMatch(actualDD, expectedDD); + }); + + it("Should allow the Pool Owner Treasury to make payment even if the receivable is not just minted", async function () { + const oldCR = await creditContract.getCreditRecord(creditHash); + const oldDD = await creditContract.getDueDetail(creditHash); + const paymentAmount = oldCR.yieldDue; + // Declare payment on the receivable so it's partially paid. + await receivableContract + .connect(borrower) + .declarePayment(receivableId, toToken(1)); + + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + paymentAmount, + ), + ) + .to.emit(creditContract, "PaymentMade") + .withArgs( + await borrower.getAddress(), + await poolOwnerTreasury.getAddress(), + paymentAmount, + oldCR.yieldDue, + 0, + 0, + 0, + 0, + 0, + await poolOwnerTreasury.getAddress(), + ) + .to.emit(creditContract, "PaymentMadeOnBehalfOfWithReceivable") + .withArgs( + await borrower.getAddress(), + receivableId, + paymentAmount, + await poolOwnerTreasury.getAddress(), + ); + + const actualCR = await creditContract.getCreditRecord(creditHash); + const expectedCR = { + ...oldCR, + ...{ + nextDue: oldCR.nextDue.sub(oldCR.yieldDue), + yieldDue: 0, + }, + }; + checkCreditRecordsMatch(actualCR, expectedCR); + + const actualDD = await creditContract.getDueDetail(creditHash); + const expectedDD = { + ...oldDD, + ...{ + paid: paymentAmount, + }, + }; + checkDueDetailsMatch(actualDD, expectedDD); + }); + + it("Should not allow payment when the protocol is paused or the pool is not on", async function () { + await humaConfigContract.connect(protocolOwner).pause(); + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + borrowAmount, + ), + ).to.be.revertedWithCustomError(poolConfigContract, "ProtocolIsPaused"); + await humaConfigContract.connect(protocolOwner).unpause(); + + await poolContract.connect(poolOwner).disablePool(); + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + borrowAmount, + ), + ).to.be.revertedWithCustomError(poolConfigContract, "PoolIsNotOn"); + await poolContract.connect(poolOwner).enablePool(); + }); + + it("Should not allow payment by non-Pool Owner Treasury", async function () { + await expect( + creditContract + .connect(borrower) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId, + borrowAmount, + ), + ).to.be.revertedWithCustomError(creditContract, "PoolOwnerTreasuryRequired"); + }); + + it("Should not allow payment with 0 receivable ID", async function () { + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + 0, + borrowAmount, + ), + ).to.be.revertedWithCustomError(creditContract, "ZeroReceivableIdProvided"); + }); + + it("Should not allow payment if the receivable wasn't transferred to the contract", async function () { + // Create another receivable that wasn't used for drawdown, hence not transferred to the contract. + await receivableContract + .connect(borrower) + .createReceivable(2, borrowAmount, maturityDate, "", ""); + const balance = await receivableContract.balanceOf(borrower.getAddress()); + expect(balance).to.equal(1); + const receivableId2 = await receivableContract.tokenOfOwnerByIndex( + borrower.getAddress(), + 0, + ); + await receivableContract + .connect(borrower) + .approve(creditContract.address, receivableId2); + await creditManagerContract + .connect(evaluationAgent) + .approveReceivable(borrower.getAddress(), receivableId2); + + await expect( + creditContract + .connect(poolOwnerTreasury) + .makePaymentOnBehalfOfWithReceivable( + borrower.getAddress(), + receivableId2, + borrowAmount, + ), + ).to.be.revertedWithCustomError(creditContract, "ReceivableOwnerRequired"); + + await receivableContract.connect(borrower).burn(receivableId2); + }); + }); + }); + describe("makePrincipalPaymentWithReceivable", function () { const yieldInBps = 1217, principalRate = 100, From e12a1cf4586777f3cb19312e8e7670897bd834d5 Mon Sep 17 00:00:00 2001 From: Jiatu Date: Thu, 19 Dec 2024 10:34:37 +0800 Subject: [PATCH 3/4] Remove TODO --- contracts/liquidity/TrancheVault.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/liquidity/TrancheVault.sol b/contracts/liquidity/TrancheVault.sol index 5b08b9b9..975dfa85 100644 --- a/contracts/liquidity/TrancheVault.sol +++ b/contracts/liquidity/TrancheVault.sol @@ -506,7 +506,6 @@ contract TrancheVault is DepositRecord memory depositRecord = _getDepositRecord(lender); uint256 principalWithDecimals = depositRecord.principal * DEFAULT_DECIMALS_FACTOR; if (assetsWithDecimals > principalWithDecimals) { - // TODO(jiatu): Check yield > 0 instead? uint256 yieldWithDecimals = assetsWithDecimals - principalWithDecimals; uint256 yield = yieldWithDecimals / DEFAULT_DECIMALS_FACTOR; // Round up the number of shares the lender has to burn in order to receive From a5fdfbf1a52e6f48f2983e9f618498e14a8dabf2 Mon Sep 17 00:00:00 2001 From: Jiatu Date: Thu, 19 Dec 2024 11:35:23 +0800 Subject: [PATCH 4/4] Assert borrower balance doesn't change --- test/unit/credit/CreditLineTest.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/credit/CreditLineTest.ts b/test/unit/credit/CreditLineTest.ts index 2a1c4c0e..9d76b05b 100644 --- a/test/unit/credit/CreditLineTest.ts +++ b/test/unit/credit/CreditLineTest.ts @@ -7623,6 +7623,7 @@ describe("CreditLine Test", function () { const oldPoolOwnerTreasuryBalance = await mockTokenContract.balanceOf( poolOwnerTreasury.getAddress(), ); + const oldBorrowerBalance = await mockTokenContract.balanceOf(borrower.getAddress()); if (paymentAmountUsed.gt(ethers.constants.Zero)) { let poolDistributionEventName = ""; @@ -7686,6 +7687,8 @@ describe("CreditLine Test", function () { expect(oldPoolOwnerTreasuryBalance.sub(newPoolOwnerTreasuryBalance)).to.equal( paymentAmountUsed, ); + const newBorrowerBalance = await mockTokenContract.balanceOf(borrower.getAddress()); + expect(oldBorrowerBalance).to.equal(newBorrowerBalance); const newCR = await creditContract.getCreditRecord(creditHash); let periodsPassed = 0;