-
Notifications
You must be signed in to change notification settings - Fork 233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: fusdc advancer with fees #10494
Conversation
165b3d7
to
99707ef
Compare
8fcf7cb
to
06ca4dd
Compare
Deploying agoric-sdk with
|
Latest commit: |
3771e7f
|
Status: | ✅ Deploy successful! |
Preview URL: | https://d3aaff76.agoric-sdk.pages.dev |
Branch Preview URL: | https://pc-fusdc-fees.agoric-sdk.pages.dev |
Base branch is changed to master. Please re-run the integration tests by adding 'force:integration' label. |
06ca4dd
to
31a6f68
Compare
export const meta = { | ||
customTermsShape: { | ||
contractFee: NatAmountShape, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yes, they should never have been terms because they must be changeable
@@ -53,6 +50,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |||
const terms = zcf.getTerms(); | |||
assert('USDC' in terms.brands, 'no USDC brand'); | |||
assert('usdcDenom' in terms, 'no usdcDenom'); | |||
mustMatch(privateArgs.feeConfig, FeeConfigShape, 'must provide feeConfig'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please instead use meta.privateArgsShape
@@ -64,6 +62,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |||
const { chainHub, vowTools } = tools; | |||
const makeAdvancer = prepareAdvancer(zone, { | |||
chainHub, | |||
feeConfig: privateArgs.feeConfig, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: cleaner to destructure in the fn params
packages/fast-usdc/src/utils/fees.js
Outdated
* @param {Amount<'nat'>} requested must be greater than 0 | ||
*/ | ||
calculateAdvanceFee(requested) { | ||
!isEqual(requested, emptyAmount) || Fail`Fee exceeds requested.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will error "Fee exceeds" if the brands don't match. The check should include brands. Consider instead a nonZeroAmount
shape made using the limits
param. Then this could handle both checks cleanly.
!isEqual(requested, emptyAmount) || Fail`Fee exceeds requested.`; | |
mustMatch(requested, nonZeroAmount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good feedback. Just now I made NonZeroAmountShape = { brand: BrandShape, value: M.gte(1n) }
and implemented mustMatch
. I made it brand agnostic as the consuming services will have done this check already.
In light of Dan's feedback, I came to realize this check was vestigial and have removed it. Some iteration of this couldn't deal with zeros during math.
}; | ||
}; | ||
|
||
const feeToolsScenario = test.macro({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect use of macros
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is how far I got before meetings...
contractFee: NatAmountShape, | ||
poolFee: NatAmountShape, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
taking these out of the terms is an unfortunate regression. I suppose I understand the motivation - they can change on restart / upgrade.
Plus, our clients can't do getTerms()
. They can only read vstorage....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the ability to change fees is the main motivation here. We could consider putting initialFeeConfig
in terms, but it seems superfluous to me.
@@ -53,6 +50,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |||
const terms = zcf.getTerms(); | |||
assert('USDC' in terms.brands, 'no USDC brand'); | |||
assert('usdcDenom' in terms, 'no usdcDenom'); | |||
mustMatch(privateArgs.feeConfig, FeeConfigShape, 'must provide feeConfig'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let Zoe do this: put FeeConfigShape
and such in meta.privateArgsShape
.
packages/fast-usdc/src/typeGuards.js
Outdated
flat: NatAmountShape, | ||
variableRate: RatioShape, | ||
maxVariable: NatAmountShape, | ||
contractRate: RatioShape, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we'd constrain the brand in all these. But we want this shape in meta.privateArgsShape
, where we don't have the brand identity.
packages/fast-usdc/src/utils/fees.js
Outdated
* @param {Amount<'nat'>} requested | ||
*/ | ||
calculateAdvance(requested) { | ||
const fee = this.calculateAdvanceFee(requested); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use this
except in exo's. (IOU pointer to docs)
Please define separate functions and then return a hardened record of them.
Or do const self = harden({ ... }); return self;
and use self.calculateAdvanceFee
. Or tools
.
packages/fast-usdc/src/utils/fees.js
Outdated
* @param {Amount<'nat'>} requested must be greater than 0 | ||
*/ | ||
calculateAdvanceFee(requested) { | ||
!isEqual(requested, emptyAmount) || Fail`Fee exceeds requested.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!isEqual(requested, emptyAmount) || Fail`Fee exceeds requested.`; | |
!isEmpty(requested) || Fail`Fee exceeds requested.`; |
Why the special case, though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL isEmpty
, although it was right in front of my face, that's handy.
why the special case
You're right, no need for this. This is vestigial from when some iteration couldn't deal with zeros.
@@ -123,14 +126,17 @@ export const prepareAdvancerKit = ( | |||
// this will throw if the bech32 prefix is not found, but is handled by the catch | |||
const destination = chainHub.makeChainAddress(EUD); | |||
const requestedAmount = toAmount(evidence.tx.amount); | |||
const advanceAmount = feeTools.calculateAdvance(requestedAmount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise, add a @throws
for calculateAdvance
, please.
// report `requestedAmount`, not `advancedAmount`... do we need to | ||
// communicate net to `StatusManger` in case fees change in between? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest adding a test to answer this question -- a test to simulate a dashboard query on output of the StatusManager
. It's possible that the dashboard requirements aren't sufficiently clear, but this would be a good form of review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simulate a dashboard query
Agree, but we have a "Status Manager writes to vstorage" task scheduled for the future. We can comprehensively tackle this then.
P.S. - this particular question is more about settling with the same fee split we used during advance. I agree we should have a test for this, but the settler is not wired up to use the FeeConfig
yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scheduled for the future
yes; my review was rushed and I didn't manage to separate things in scope for this PR from other thinking-out-loud.
does a test.todo()
seem cost-effective?
@@ -147,7 +153,7 @@ export const prepareAdvancerKit = ( | |||
} | |||
|
|||
try { | |||
const payment = await assetManagerFacet.borrow(requestedAmount); | |||
const payment = await assetManagerFacet.borrow(advanceAmount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just below this, I see poolAccount
. Its type is ERef<...>
. Do we test the promise case? I don't think promises can be stored durably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect poolAccount
to be a vow
or a remotable
. The former can be stored durably.
Look for tests in #10497, or further in the future if scope is cut, as the contract doesn't currently create an LOA.
@@ -44,6 +40,7 @@ harden(meta); | |||
* @param {ZCF<FastUsdcTerms>} zcf | |||
* @param {OrchestrationPowers & { | |||
* marshaller: Marshaller; | |||
* feeConfig: FeeConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add this to publicTopics
to be sure vstorage clients can see them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a future task "Write contract config to vstorage" where we can tackle this. I'd add a todo to the tasklist, but it's still a draft.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
packages/fast-usdc/test/mocks.ts
Outdated
variableRate: makeRatio(2n, usdc.brand, 100n, usdc.brand), | ||
maxVariable: usdc.units(100), | ||
contractRate: makeRatio(2n, usdc.brand, 10n, usdc.brand), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The denominator brand defaults to the numerator brand.
And the denominator defaults to 100n
.
variableRate: makeRatio(2n, usdc.brand, 100n, usdc.brand), | |
maxVariable: usdc.units(100), | |
contractRate: makeRatio(2n, usdc.brand, 10n, usdc.brand), | |
variableRate: makeRatio(2n, usdc.brand), | |
maxVariable: usdc.units(100), | |
contractRate: makeRatio(20n, usdc.brand), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good tip, thanks
3ad5d52
to
a1d931d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup. let's do this.
|
||
/** @type {TypedPattern<string>} */ | ||
export const EvmHashShape = M.string({ | ||
stringLengthLimit: 66, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting... We don't have patterns for "starts with xyz" and the like, so I had only thought of string length limits as denial-of-service protection.
const NatAmountShape = { brand: BrandShape, value: M.nat() }; | ||
export const meta = { | ||
|
||
export const meta = /** @type {ContractMeta<FastUsdcSF>} */ ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is this better than the existing convention?
export const meta = /** @type {ContractMeta<FastUsdcSF>} */ ({ | |
/** @type {ContractMeta<typeof start>} */ |
packages/fast-usdc/src/utils/fees.js
Outdated
calculateAdvanceFee(requested) { | ||
const variable = min(multiplyBy(requested, variableRate), maxVariable); | ||
const fee = add(variable, flat); | ||
!isGTE(fee, requested) || Fail`Fee exceeds requested.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If fee == requested
then isGTE will be true, so !isGTE will be false and the Fail will throw.
!isGTE(fee, requested) || Fail`Fee exceeds requested.`; | |
isGTE(requested, fee) || Fail`Fee exceeds requested.`; |
Or,
!isGTE(fee, requested) || Fail`Fee exceeds requested.`; | |
!isGTE(fee, requested) || Fail`Request must exceed fees.`; |
If the latter, updates the @throws
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the error message and @throws
- useful for `privateArgsShape` in consuming contracts - renames cosmos `orchestrationService`s powers from `OrchestrationPowers` -> `CosmosOrchestrationPowers`
a1d931d
to
cd0c2bf
Compare
cd0c2bf
to
3771e7f
Compare
refs: #10390
Description
FeeConfig
toprivateArgs
so deployers can adjust fees over time (terms are immutable)FeeTools
for calculating net advance and fee splitsFeeTools
withAdvancer
Security Considerations
None really for these specific changes. We might need to think about timing from when the
Advancer
callscalculateAdvance
and theSettler
callscalculateSplit
, as theFeeConfig
values might change.From a product POV, users might pay more in fees than the net advance they receive. So we might consider a config parameter for a "minimum request amount".
Scaling Considerations
None really, mainly contains AmountMath computation.
Documentation Considerations
Includes jsdoc and code comments
Testing Considerations
Includes unit tests for FeeTools, attempting to cover all foreseeable scenarios. A bit light on testing integrations with the advancer and settler.
Upgrade Considerations
N/A, unreleased