From 118e6ada3b0febfaeec59d2fcef45b05757b83d8 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Mon, 19 Aug 2024 14:18:30 -0700 Subject: [PATCH] feat: ratio.quantize() shouldn't increase precision unnecessarily --- .../test/auction/sortedOffers.test.js | 18 ++++---- .../snapshots/vaultFactory.test.js.md | 12 ++--- .../snapshots/vaultFactory.test.js.snap | Bin 4136 -> 4161 bytes packages/zoe/src/contractSupport/ratio.js | 7 ++- .../unitTests/contractSupport/ratio.test.js | 41 +++++++++++++----- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/packages/inter-protocol/test/auction/sortedOffers.test.js b/packages/inter-protocol/test/auction/sortedOffers.test.js index 260aac80850..7dc908b617e 100644 --- a/packages/inter-protocol/test/auction/sortedOffers.test.js +++ b/packages/inter-protocol/test/auction/sortedOffers.test.js @@ -4,8 +4,10 @@ import { ratiosSame, makeRatioFromAmounts, quantize, + subtractRatios, } from '@agoric/zoe/src/contractSupport/index.js'; import { setup } from '@agoric/zoe/test/unitTests/setupBasicMints.js'; +import { AmountMath } from '@agoric/ertp'; import { fromPriceOfferKey, toPriceOfferKey, @@ -65,6 +67,10 @@ test('toKey discount', t => { t.true(keyD26 > keyD25); }); +const ratiosEqual = (t, left, right) => { + t.true(AmountMath.isEmpty(subtractRatios(left, right).numerator)); +}; + test('fromKey Price', t => { const { moola, moolaKit, simoleans, simoleanKit } = setup(); const { brand: moolaBrand } = moolaKit; @@ -81,12 +87,7 @@ test('fromKey Price', t => { t.true( ratiosSame(priceAOut, makeRatioFromAmounts(moola(40n * N), simoleans(N))), ); - t.true( - ratiosSame( - priceBOut, - quantize(makeRatioFromAmounts(moola(40n), simoleans(1000n)), N), - ), - ); + ratiosEqual(t, priceBOut, makeRatioFromAmounts(moola(40n), simoleans(1000n))); t.is(timeA, DEC25); t.is(timeB, DEC25); }); @@ -104,8 +105,9 @@ test('fromKey discount', t => { const [discountAOut, timeA] = fromScaledRateOfferKey(keyA25, moolaBrand, 9); const [discountBOut, timeB] = fromScaledRateOfferKey(keyB25, moolaBrand, 9); - t.deepEqual(quantize(discountAOut, 10000n), quantize(fivePercent, 10000n)); - t.deepEqual( + ratiosEqual(t, discountAOut, fivePercent); + ratiosEqual( + t, quantize(discountBOut, 10000n), quantize(fivePointFivePercent, 10000n), ); diff --git a/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.md b/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.md index f8f766c8bfa..d814867d411 100644 --- a/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.md +++ b/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.md @@ -72,11 +72,11 @@ Generated by [AVA](https://avajs.dev). compoundedInterest: { denominator: { brand: Object @Alleged: IST brand {}, - value: 100000000000000000000n, + value: 857701650301172500n, }, numerator: { brand: Object @Alleged: IST brand {}, - value: 101967213114754098360n, + value: 874574469651359500n, }, }, interestRate: { @@ -387,11 +387,11 @@ Generated by [AVA](https://avajs.dev). interest: { denominator: { brand: Object @Alleged: IST brand {}, - value: 100000000000000000000n, + value: 857701650301172500n, }, numerator: { brand: Object @Alleged: IST brand {}, - value: 101967213114754098360n, + value: 874574469651359500n, }, }, }, @@ -413,11 +413,11 @@ Generated by [AVA](https://avajs.dev). interest: { denominator: { brand: Object @Alleged: IST brand {}, - value: 100000000000000000000n, + value: 857701650301172500n, }, numerator: { brand: Object @Alleged: IST brand {}, - value: 101967213114754098360n, + value: 874574469651359500n, }, }, }, diff --git a/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.snap b/packages/inter-protocol/test/vaultFactory/snapshots/vaultFactory.test.js.snap index cd6de400d63fb49fe135c1e817ee17495c4febe4..a5a8485190576a42b367f995d1e1ba27eaf46f6d 100644 GIT binary patch literal 4161 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 L85fE#2}A$@U1q;O literal 4136 zcmV+@5ZCWPRzVMcMe0m#;Mdhx}-bcpsQH}_d zrd8lcphUN(peBkdvh}InYDNDN9q@rX*$Q!Gwm!Ne`S* z%6nst-rOrac^L7>=z8ww_x=9feQ)0V-M8?_p5bsba9Ql&qnZ&i!rFe{m>N+>G~E}8 z8lgQQO^-GE_Jxd5Uo4`I$3~+@XtEJI_-HVw8tVCLKXH+b`Ja-bcrFBnfiSQSxDL1j zI0`%l{55bKcma3?coTRBSfFGTDeggCR}U!4WXtRIqU50zqU0;e@8c%93R`YJNnQ1kDWH#Ma^Ifxpgl7g z91y|2Ojwr**9+ig5j>m;cW1)C3g87I_--bAkO>{`WH2a#{cc$AhT8-%B5k!Bj=AAK z1#rIze&~jiZdjd_1ZG@^NN&u6&7V_u3FPe(`Ct}2o&_fa@?MGjQx^O=3!1W%$%hdS z)(rCqS)gQ4Y#xm6B4zRT#Bewi8`Xj>d)0}su~7{eQT;&6NOZ5JN7P6_vyQE znFYH}ThQu`#s9>|bLE`twpqHPbFSNN>E1Wzx*eA8&*xmX)6)Ipoa_26-J0CF*6p%% zcRJ`Unn~Ao;NN&EK1a!TDi@y1J#|XS+B$rx7BCc7qB(EgJQK_OWiGst3!mgdd7gk* ziFihT9;_F!S`qVJlm~%4xHS)+%!8Nn;A9@0kq?{m;fj2CC?9^35AWmy1yEZ6TM7i} zHKMd@q5!TafTJS1McTG!3*h+zc(VZhN5sD@;Z=pOxDW;kVRxZ`YZ88KAsi}%#|zZd-Z1X_G1BV1W<9a0idmebk1Fw4Egvj3^^Tl4M^g@pp zws{5qK8bf7@WM4-I3}`>2yCHs0D2VVXI>J=ZgyZo*R_aYb-S$YPi@`v1JSYZ=tLx_ z1vfGtU>Cu3u1!GBA(flc_)WWupMs;J48V*l!h1TiUqbR*LI^U`X z2FIckk$7Gsw$1{pbDMf#EcqzE$Ib+`VPpHLMUK_`6I*Xq{D2YmQl53H-)^XCK1bitgM3@&go3D1qmeU2tSn;Vl49Zf2W=fq@Vv6G2DHzp4{n!NOdOakF( zEa^A!C!0)uAo0tMS)J7-w%?|%?NjyOwrDgw7!F4R)_KL8JhIo9z}Y2mX$c%Efp3+- zFG}E@5@8<9l;)khQkYi?%S#2cT0+k%g>y>bG7@Ic&QwIBcf+XsH*~UE5KJFpqnIgpaM2jz=a}uwuD|$ z0S7DKsEB?^LZ7REA639>B6^O1Cik9Sq?8VZ!`g@zT;;P)?Y{B&xqVXjLfz(YIs>>kIL+O)$qG&$XqC}4@zvt(uL44$(_0+ z^r(38D5GN`tQ6HAmrL?2>ui1Q!l|>hXWgi(kAxy4+cZ5CjTc8(+WPZ0gf(-4IazK0 zg*|plD6%OMGD5a0?VoJ@-XSdz)q~06pW1+eA#IPQYmtB!+_@=U>MybtiHVYIQ)F+* zI5m$XCZz2)G(V_CqGO?mYDD#Tp>ONVjZBPbdb}$U{g+PBuQ^61&7C>waBPdPEr52mj-qULB(}chsX_()YT5M)pS8I>Glgd_Nv9xxqf>V*H=?> z&1!2EZ2xas+lD31wmX)jwy|x3>orTJbN$XNuCJ%*sw|z(b@@_f*YlUAIm9z!qAVj1Mu z2$h>#s#sRkfWHPV6wxdRjn%+`8aO7R9tnNA29DRjzldm&gnn8Bu3D(A70~$-x~&#= z)xuRGx?U?rpLiw&1=N#^tC5JPbp{5ynMcw=5 z9KPHPYBO9dvfpsd;UUKylG(>*$@l(dc&Hhk6?1q(UZ9K1%S_|Ic;ce0E24_`mxP+9 ztF^Ods`{8&DKBJOQJZym^GtR4%ol2WRtsF*WZDf6`_Z(rLi-kaOf*zA364%Uy&X8lTP)=Af>K5=rcZJ)(? zs6EA#tiR2w4z6m4>)YXhcA;3zmhUmN!*lKMk0R=o&=1?;WIN<{2xzfm4ZO&40kkOj zGe>bP>45qU7!Yk5q>O?caA^nJB%;j{`snAX-|m3F7tvM;eX9fB?EqJ&fObmgqE1-W z34CBci<`>VBXT9`1x6bi(VM@PQa|uH%4TJoA9(D6VWj6#8MAUl9F@ zl=22Ye8~^PBC0z2k2?Ba?}uCb@USR)spS7tKm5WEzZTKU9Q}1i|Ihs3?t~1x@GyO)4I!uJmt(;{d{H;@T;i_4pdsaC_2WE>t>=b=`w&=H=qP?qUjLwWd z0BHkq;_I7D@x`NN4~_S1oro`fj@Y`~)|FiA>*wte=GQSp9UHf$`rhgktBs@`{JXTG z|BzPn$7w}>mR7X8H=XGGw4#gBiY`kldQn=@HK|3NcWj*K18y$0;->SLEPpC9< zij;+k`Q?JB=9_w`CHaQHU@x56n|wncH~EG@vhqB~qCLkFJ+~Kj3!)`XqK%F-vf2wH zy>NA}Q0*+2Cgg{D;fY>&UPN0Y^xt~nx4rPOh_*>+WgqzZpu0~%+a>h8KDf9KCPdUP zp-21R!9I9KM7t#PwLW;W4?Ysnl@dC?AIke-MZbWqlF+UFu(Kb+BHAaRclN_Q{qS88 z?U&G(`{C7ocwa<^B-A?qB?HhrAfP)O%jjM9n_OGe$b=f+VNPa=?HYic1F%n&-JL8u z59MOp=cw&dw|!nY^_g)9`=298e;%0nlejE)*MN0dEb;H34Iq?)S@%m?7)qfAX1+|S85^Ve zkTxFG&G*a_kKo4N^B7q>eO^Z$^ZJMB^AbifzeuT=8uLVPbe82Z-Ov{lDOHJpwG$?` z$!0xWNMVsuX7h+=GP6TjswtJHo>GNlN?KSQ*24Q=-5CjLx^GG1-QOk6K4Vn#EwR4> zyu=rY1~p$O<_l_JZA3M+pwEa}oQ3)J#5rFe8ZmS=V6^1z7}b2*e${;C&o``vqx*cJ zaCjnSn2&qMtjJhkR2x%$(LL6`%>N&UFOh`#m9Dkq9tpjSg#iP>Mt>(K6QLtQ}s(4TK*rJ*N~$dL;wJnMlFs2 diff --git a/packages/zoe/src/contractSupport/ratio.js b/packages/zoe/src/contractSupport/ratio.js index 46e0281c04d..72dd7b22498 100644 --- a/packages/zoe/src/contractSupport/ratio.js +++ b/packages/zoe/src/contractSupport/ratio.js @@ -343,7 +343,8 @@ export const ratiosSame = (left, right) => { }; /** - * Make an equivalant ratio with a new denominator + * Make a new ratio with a smaller denominator that approximates the ratio. If + * the proposed denominator is larger than the current one, return the original. * * @param {Ratio} ratio * @param {bigint} newDen @@ -352,6 +353,10 @@ export const ratiosSame = (left, right) => { export const quantize = (ratio, newDen) => { const oldDen = ratio.denominator.value; const oldNum = ratio.numerator.value; + if (newDen > oldDen) { + return ratio; + } + const newNum = newDen === oldDen ? oldNum : bankersDivide(oldNum * newDen, oldDen); return makeRatio( diff --git a/packages/zoe/test/unitTests/contractSupport/ratio.test.js b/packages/zoe/test/unitTests/contractSupport/ratio.test.js index 62b9ce9e56d..8a4632b5fc5 100644 --- a/packages/zoe/test/unitTests/contractSupport/ratio.test.js +++ b/packages/zoe/test/unitTests/contractSupport/ratio.test.js @@ -501,25 +501,25 @@ const { brand } = makeIssuerKit('moe'); test('ratio - quantize', t => { /** @type {Array<[numBefore: bigint, denBefore: bigint, numAfter: bigint, denAfter: bigint]>} */ - const cases = /** @type {const} */ [ + const cases = [ [1n, 1n, 1n, 1n], [10n, 10n, 10n, 10n], [2n * 10n ** 9n, 1n * 10n ** 9n, 20n, 10n], - [12345n, 12345n, 100n, 100n], - [12345n, 12345n, 100000n, 100000n], - [12345n, 12345n, 10n ** 15n, 10n ** 15n], - - [12345n, 123n, 100365854n, 10n ** 6n], - [12345n, 123n, 10036585n, 10n ** 5n], - [12345n, 123n, 1003659n, 10n ** 4n], - [12345n, 123n, 100366n, 10n ** 3n], [12345n, 123n, 10037n, 10n ** 2n], [12345n, 123n, 1004n, 10n ** 1n], [12345n, 123n, 100n, 10n ** 0n], + + [12345n, 12345n, 100n, 100n], ]; - for (const [numBefore, denBefore, numAfter, denAfter] of cases) { + for (const [ + numBefore, + denBefore, + numAfter, + target, + denAfter = target, + ] of cases) { const before = makeRatio(numBefore, brand, denBefore, brand); const after = makeRatio(numAfter, brand, denAfter, brand); t.deepEqual( @@ -530,6 +530,27 @@ test('ratio - quantize', t => { } }); +test('ratio - quantize - leave it alone', t => { + const cases = [ + [12345n, 123n, 10n ** 5n, 12345n, 123n], + [12345n, 123n, 10n ** 4n, 12345n, 123n], + [12345n, 123n, 10n ** 3n, 12345n, 123n], + + [12345n, 12345n, 100_000n, 12345n, 12345n], + [12345n, 12345n, 10n ** 15n, 12345n, 12345n], + ]; + + for (const [numPre, denPre, qTarget, numAfter, denAfter] of cases) { + const before = makeRatio(numPre, brand, denPre, brand); + const after = makeRatio(numAfter, brand, denAfter, brand); + t.deepEqual( + quantize(before, qTarget), + after, + `${numPre}/${denPre} quantized to ${qTarget} should be ${numAfter}/${denAfter}`, + ); + } +}); + test('ratio - parse', t => { const { brand: moeBrand } = makeIssuerKit('moe'); const { brand: larryBrand } = makeIssuerKit('larry');