-
Notifications
You must be signed in to change notification settings - Fork 220
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
EPIC: Zoe Design Doc - Paying for Metering #3294
Comments
This feels like the right start for a good user facing approach. A design constraint I'd like to try to add: can we mostly or fully eliminate the chance that a user pays for activity that doesn't result in an actual transaction? I.e., paying for a price quote on the AMM is a non-starter (though the solution in this case is to not require a roundtrip to the chain for a price quote, so we can handle that separately). For all its flaws, the Ethereum model mostly achieves this by having users sign transactions ahead of time and only paying if they get processed. In discussing reducing roundtrips to the chain for performance reasons during the last testnet phase, we talked about an offer model where the wallet might present an offer to the user upfront for pre-approval, which seems to push in the same direction here. |
Let's talk this through, because the version above does require the user to pay for a price quote on the AMM, which requires a query sent to the chain, so that would be (part of) a transaction. The only ways I can see to avoid that are: 1) the dapp has a backend that does the query where the dapp developer pays for the transaction on the user's behalf, and the dapp requires the user to login such that it can cut off access if necessary, or 2) we create a mechanism to read the chain at the JavaScript level without creating transactions. Perhaps event logging could provide this, but we don't have anything like this now. |
A few questions:
That is all for now. |
Ideas from today's meeting (@katelynsills @dtribble @mhofman @warner):
The swingset support is:
Other notes:
|
The current
(the In #3308 I'm proposing to augment that to:
@katelynsills would that be sufficient for the rest of the When Zoe creates a new ZCF vat, it would do: const meter = await E(vatAdmin).createMeter({ capacity, notificationThreshold });
const { adminNode, root } = await E(vatAdmin).createVat(code, { meter }); then async function react() {
const computronsBought = await sellRUN(contractOwnerChargeAccount, RUNToSell);
await E(meter).addCapacity(computronsBought);
} And when the user code initiates a new action (claiming an offer or something), Zoe does something similar, but selling RUN from the user's Let me know what you think, and @mhofman and I will get started on implementing the kernel-side pieces. |
@warner, this sounds good to me. There may be hiccups when implementing that may necessitate some small changes but this sounds like a great place to start. |
Some more thoughts on pricing: The user and the contract creator have opposing desires regarding the timing of fee menus. For instance, the user would like to know the cost of making an offer with a particular invitation, as far ahead of time as possible. Ideally for the user, the fee would be immutable and in the invitation's details along with the instance and installation. This is the opposite of what is good for the contract creator. With potentially fluctuating prices for computrons, and with potentially fluctuating computrons per offerHandler (for instance, a particular offer sets off more processing within the contract), the contract creator desires to put off pricing as late as possible. This makes sense if we view the quoted fee as an option. Options over longer periods of time are more costly for the entity offering the option, because that entity is taking on more risk and giving up more opportunities. So where and when should Zoe require quotes for fees (menus)? Here are some possibilities:
We can throw this possibility out. Invitations might be held for years, during which the costs to the contract developer might change drastically.
This is unacceptable from the user's perspective, for a number of reasons. First, maybe they had to pay some money to get the invitation in the first place, and they probably can't get a quote ahead of time for how much using the invitation would cost them. Now the contract creator can charge exorbitant prices for using the invitation, and the user has two bad choices: walk away having spent money on the invitation, or go forward paying even more fees. Second, if the fee function is entirely controlled by the contract creator, it's not clear what the user is agreeing to. Let's say the user queries for the current menu of prices, gets a price, and then makes an offer. In the meantime, the contract creator jacks up the prices, and the user's chargeAccount is charged the much higher price. Side note: a great attack from the perspective of the contract creator against users would be to take something like the Vault, and make it cheap for users to escrow their collateral, and then dynamically make it prohibitively expensive for them to withdraw it, and take their collateral or wait for it to liquidate.
This follows the pattern of offers. Users are free to make whatever offers they want, and the contract code is free to reject it by exiting the seat immediately. Users are assisted in putting their offer together by dapps. The downside is that there is no ergonomic way to specify the fee you are willing to pay, when calling methods on the publicFacet. This only really works for offers, and if you're specifying the fee, you might as well send a payment directly rather than a chargeAccount.
|
Another potential attack: if fees are charged no matter what, and fees are high, a contract creator could trick users into paying fees for an opportunity that doesn't actually exist. For example, the contract creator would present something that looks like great opportunity, such as token sold at a low price, get users to make an offer, then subtly exit the seat so that the user gets their original allocation, but is charged the fee. In this case, offer safety still holds, but does not include a refund of the fee. For the attack to work, the fact that the seat is immediately exited will have to be obscured somehow. Our usage of installations with petnames might mitigate this somewhat. |
Hmm... if computrons was its own currency (fungible ERTP right) then its fluctuating price in RUN would not be an issue, I hold forth. However, the problem of exercise of an invitation or use needing more computron fuel to burn due to more activity for that use, is bit harder to solve. |
@nathan-at-least asked What are the costs for using the network and who pays them? Are they transaction fees? As a contract developer, are there issues I need to be aware of around these costs? Do I need to consider optimization at the JS source code level?I think this issue is most relevant to the question, but other metering issues are likely to be relevant as well. One perspective I learned in a meeting last week is that fees come in roughly two kinds:
Contracts run on a meter, and when the meter runs out, they stop computing. (Whether this is a fatal or recoverable error is TBD - someone could perhaps refill their meter to allow them to resume.) Contracts can choose how much of this cost to pass on to users / clients vs. using, for example, auction fees. Yes, JS optimization is relevant. Execution fees are intended to incentivize efficient computation. The JavaScript engine we use, XS, is instrumented to meter every step in a JavaScript computation. Note that while the chain must be protected from runaway computation and from spam / griefing, we don't intend that execution fees are the primary incentive for validators / stakers. Stakers should get rewarded for value produced rather than for labor spent. Hm... our Economy & Network page doesn't tell that story as well as I'd like. I think Dean's Dec 2020 Pillar Series: Public Chain and Economy talk is better. We aim to use the escalator algorithm from Drexler and Miller 1988. It allows clients to pay for priority. #3530 is scoped more precisely to escalators. |
Here's the documentation we have on fees and metering: https://agoric.com/documentation/zoe/api/fees-and-metering.html |
Thanks. This is very helpful! |
For the purposes of this document, let's define:
E(zoe).install(...)
.E(zoe).startInstance(...)
E(zoe).offer(...)
with an invitation to this contractSummary
(Note: Items have been marked as IN-SCOPE or OUT-OF-SCOPE for the metering testnet phase.)
chargeAccount
is a purse-like object known to Zoe that holds RUN and can be charged to pay for metering.Add feePurse #3309
Add feePurse arg to Zoe methods. Also partially apply the feePurse to produce original Zoe API #3322
An Aside: Best Practices for Charging Fees
Charging unanticipated and surprising high fees for services already provided makes users unhappy. A much better user experience is giving a quote ahead of providing the service, then only charging the quoted amount. The service provider covers any actual difference in the quoted price compared to the actual costs.
This means that whatever users agree to when they approve something in their wallet is what they should be charged. In other words, we shouldn't have a model where the users' purse or chargeAccount is charged the current price of execution, if that price is different than what the user agreed to. Volatility shouldn't be pushed onto the user after they've made a decision. (edit: there are at least two kinds of surprises: 1) an increase in the number of units of execution over what was estimated, and 2) an increase in the cost of a single unit of execution. We need to make sure to handle both cases.) Instead, the price that they decide to pay should already reflect the potential for future volatility while their transaction is executing. This requires 1) the contract developer be accurate in their relative estimations, and 2) the contract creator be willing to cover the difference.
Counter Argument: making the user think about fees is a huge burden on them
This is unavoidable, unless the dapp developer or contract creator choose to cover all fees and handle anti-spam measures themselves or hide the fees in the costs of other assets. That seems incredibly difficult.
But, even within this model, it is possible for the contract creator to cover all fees, merely by not setting a fee per offer or setting the fee for a publicFacet method call to 0. This may be appropriate for a short-lived contract like a covered call, but more analysis is required.
Details: Translating RUN and chargeAccounts to metering [TODO: fill in]
makeZoe
as an argument, and is static for now.Kernel implementation
#3508
A walk through the user experience of creating a vault with metering/fees:
#3399 (comment)
Incidental changes
makeZoe
and not within a contract on top of Zoe.zcf.makeInvitation
needs to take an invitation config because adding a fee with expiration is too many parameters. (Note: we could continue with simply having more parameters if that is less of a breaking change.)More detailed considerations and alternatives
See this Google doc for this Github issue's previous content, which was walking through the design from first principles and considering alternatives.
The text was updated successfully, but these errors were encountered: