-
Notifications
You must be signed in to change notification settings - Fork 31
feat(udt): balance related util methods #228
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
Conversation
🦋 Changeset detectedLatest commit: 2eca243 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
7fc2b2b
to
03fb146
Compare
Replying to the comment on the issue (and on the code):
I'm reading thru the PR and I see you were thinking about deprecating those Balance methods on Transaction, which in a way indeed solves the async issue. (Nice new TypeDocs by the way!! 🥳) Generally speaking, in
These days I was also considering letting iCKB UDT subclass also add iCKB Deposit Receipts if needed (as first choice even before actual UDT cells). If I was to do that then I would indeed need to modify Side Note: of course I see a future where we'll move everything to the witness, once CKB is too expensive. Sometimes I wonder if it makes sense calling these utilities
Nice, I see that you refined it similarly to the way I coded it, so by calculating the capacity of these cells, not by counting them 👍 Just, if I can add something in this regard, I'd say that you should segregate the concerns in the implementation so that sub-classes can reuse |
packages/udt/src/udt/index.ts
Outdated
* 1. The cell's type script matches this UDT's script | ||
* 2. The output data is at least 16 bytes long (required for UDT balance storage) | ||
*/ | ||
isUdtCell(cellOutputLike: ccc.CellOutputLike, outputData: ccc.HexLike) { |
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.
Why not using CellLike?
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.
It's more like a CellOnchain
, which contains OutPoint
of the cell, and it's not necessary for checking. I think it's meaningless to add CellOffchain
, so output + data is used here.
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.
You make a fair point, it's not a given this entities have a Outpoint. In my implementation it did, cause it was only used on on-chain cells
* This method only counts inputs that have the same type script as this UDT instance. | ||
* Inputs without a type script or with different type scripts are ignored. | ||
*/ | ||
async getInputsBalance( |
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.
Why not also returning the CKB occupied by said UDT (or UDT-related) cells?
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 idea!
packages/udt/src/udt/index.ts
Outdated
* Outputs without a type script or with different type scripts are ignored. | ||
* This is a synchronous method since output data is already available in the transaction. | ||
*/ | ||
getOutputsBalance(txLike: ccc.TransactionLike): ccc.Num { |
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.
For future SSRI compatibility we should make it async
* - Detecting minting operations (negative burned balance) | ||
* - Ensuring sufficient UDT inputs are provided for transfers | ||
*/ | ||
async getBalanceBurned( |
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 would call it getBalanceDelta, cause this delta may indeed be positive or negative
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.
Also if we add the CKB to the input and output methods, also this one should return its delta
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.
Once in a while, I name it balanceDiff
. I think delta has the same problem as diff, in that we can't intuitively understand whether it represents input minus output or vice versa.
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.
Delta usually is defined Output minus Input, differently from diff, which has no such connotation. That said, Burned is surely more colorful and probably intuitive, maybe it'll cause also more raised eyebrows, but nothing a good TypeDoc can't fix
packages/udt/src/udt/index.ts
Outdated
return 0; | ||
} | ||
|
||
const { addedCount, accumulated } = await tx.completeInputs( |
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 difficult to override, it could make sense to partition this in multiple overridable methods
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.
For example it could make sense to have a generator methods that returns said cells wrapped, possibly wrapped into an object where each cell is annotated with its ckb and udt value
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'm not sure about what we want here. Does this mean Udt.completeInputs
is hard to override? It might be easier to understand if a possible way of splitting it could be provided.
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.
Yeah, you're right, my underlying issue is with tx.completeInputs
: it may be a fine piece of engineering, I don't like it, my bad!! 🤣
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.
Indeed, Transaction.completeInputs
has a complicated method signature to maximize its flexibility while reusing code. In most cases, I think we should override the Transaction.findCells
.
I'm not sure. While it is in line with the current Transaction methods, I don't particularly like it. To me seems orthogonal to what the other methods offer, so adding as an options seems natural. What's your reason for making it into a method? Phroi %51 |
Good question. In short, the single responsibility principle and fewer options will make the method more stable. From the implementation perspective, unlike I also agree that it's orthogonal to methods like |
Further read: https://understandlegacycode.com/blog/what-is-wrong-with-boolean-parameters/ |
Added |
Let's make an example, I don't like
I see what you mean, in your implementation |
Good idea, on one side we have |
Yes. One exception is We've been adding more unit tests recently, and splitting methods into smaller units helps us write shorter tests (and makes them easier for others to override). |
My original plan was to call the hook after each transaction creation method, like |
My eyes are burning!!! 🤣 |
const balanceBurned = | ||
(await this.getBalanceBurned(tx, from.client)) - | ||
ccc.numFrom(balanceTweak ?? 0); | ||
const capacityBurned = | ||
(await tx.getFee(from.client)) - ccc.numFrom(capacityTweak ?? 0); | ||
|
||
if (balanceBurned >= ccc.Zero && capacityBurned >= ccc.Zero) { | ||
return 0; | ||
} |
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 little piece of code is efficiently creating more UDT cells out of thin air: while we could easily compress the state by checking the UDT cells capacity flow (similar to the original @rink1969 idea), we instead are checking the global transaction capacity flow.
I'd propose to evaluate:
const [inUdt, inCkb] = await this.getInputInfo();
const [outUdt, outCkb] = await this.getOutputInfo();
const udtBurned = inUdt - outUdt - ccc.numFrom(balanceTweak ?? 0);// Why not a Num in the first place?
const ckbBurned = inCkb - outCkb - ccc.numFrom(capacityTweak ?? 0);// Why not a Num in the first place?
if (udtBurned >= ccc.Zero && ckbBurned >= ccc.Zero) {
return 0;
}
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 thought compressing the UDT much is better. E.g., if I have ten UDT cells with 200 CKBs and one 70 CKB cell (for a fee) and want to transfer some CKB to others (while transferring UDT), I can merge cells and extract CKBs.
For a typical UDT transferring transaction composition, we have only one UDT cell in the output when we call Udt.complete
, so these two implementations behave the same. However, if a developer were to compose a non-typical transaction, such as transferring UDT and CKB simultaneously, I'm not sure which one is better. Personally, I prefer the current approach, which prioritizes the compression of UDT to obtain more CKB.
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 can live with both, there is no difference in complexity or efficiency.
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 prefer the current approach, which prioritizes the compression of UDT to obtain more CKB
Could you make a full example of how the code in lines +694 to +702 compress more? 🤔
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.
e.g. If we have ten 100 UDT cells with 140 CKB for each
For a meta transaction A
Inputs: -
Outputs:
150 UDT in 140 CKB
After Udt.completeBy
, we have
Transaction A1
Inputs:
2 * 100 UDT in 140 CKB
Outputs:
150 UDT in 140 CKB
50 UDT in 140 CKB
This is the same for both implementations.
But for Transaction B
Inputs: -
Outputs:
150 UDT in 140 CKB
280 CKB
After the current Udt.completeBy
, we have
Transaction B1
Inputs:
4 * 100 UDT in 140 CKB
Outputs:
150 UDT in 140 CKB
280 CKB
250 UDT in 140 CKB
If we only consider the UDT capacity flow, it will be
Transaction B2
Inputs:
2 * 100 UDT in 140 CKB
Outputs:
150 UDT in 140 CKB
280 CKB
50 UDT in 140 CKB
Obviously, B1 compressed more UDT than B2.
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'm not really sure that it makes much of a difference, cause your current algo already works pretty well
That said, if there is a chance to add more UDT cells, with this change they get added without extra RPC calls (except for the possible extra get_cells
call)
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.
It's kinda edge case if devs added some plain CKB cells along with UDT cells. Though I don't know when it will happen, it's a scenario.
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.
For example, I was thinking about NervosDAO withdrawals, like iCKB, and in the same tx the user fan out his UDT holdings
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.
A bit unrealistic tho
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.
Anyway, it won't increase the complexity, so it is worth.
For example, if we split What's your take on this? |
Also, another details, how many usecases outside of UDT we have for If all the use-cases are exclusively in UDT, why not bring that Error class definition over here and add a proper message formatting so that it can actually be displayed into an user interface? So using ccc.FixedPontToString(amount, decimals) and mentioning the symbol of the UDT causing the error. Here we are in UDT so it seems appropriate, differently from Core Transaction. Whats your take on this? |
Sounds good to me. You remind me that the current |
It's reasonable to move the Error to
Considering the extra cost (SSRI call or Indexer query), this might not. I think we should let the application do this. |
Reasonable
Not sure TBH, if you are not willing to add more metadata, it may may make more sense where it is currently. I guess that I'll consider sub-classing that error to add more metadata in my iCKB subclass 🤔 |
I wonder, what if instead we make the error a bit more general and optionally let devs specify those details in sub-classes of UDT? |
After we deprecated the
What do you think if we add a |
Maybe overriding the |
If I override Would you reconsider your previous stance? |
I don’t quite know the structure of the iCKB recipient cell. If you can provide some relevant documentation, I might be able to understand it more easily. So what you mean is that the recipe cell contains a portion of the amount that can be converted into iCKB, but this portion of the amount is not stored in the first 16 bytes? This sounds like it will cause a lot of problems, including how to generate the change cell and what to do if we want to apply the change to an existing cell. I have no idea for now and need more information. |
Here you go, feel free to ask away any doubt! 🤗
That's correct, to be precise, there is no way in which it could be calculated due to the transaction header not being accessible during transaction validation in deposit phase 1. If said header was accessible, then I would directly mint UDTs 😉
Two meanings, so I'll analyze both:
Two meanings, so I'll analyze both:
If it helps you wrapping your head around the concept, this is the iCKB Logic contract, the one validating this conversion. In general, some protocols like iCKB or dCKB need some non-UDT cells need to hold UDT value and there is no way around that, for now. For example: How can you make an NervosDAO deposit that is also an UDT? Does this help clarifying why cells that are not UDTs can still hold UDT value? Any doubts? Love & Peace, Phroi %137 |
Hey @Hanssen0, I started my implementation based on top of the Udt class and I need to give more feedback. Generally in Specifically on Udt, the default Switching to the main topic, I don't know who thought that returning a Not only errors are handled in different ways depending on the method ( Even more, non SSRI UDTs are a second thought in this implementation: an implementer cannot even add very normal xUDT metadata ( Not only that, but even sub-classing SSRI is creating troubles cause the methods for getting this metadata are async, plus given the SSRI return type signature equal pure madness. We can't even properly format I'd like to propose a simple solution:
Love & Peace, Phroi %38 |
@Hanssen0 what kind of changes would you like to make to this PR? |
I want to merge this PR as soon as we can. It has been staying here for too long and contains modifications/updates that affect other things. I understand your concerns about SSRI types, and they are reasonable. However, given that they have already been included in previous releases (so modifying them might cause compatibility trouble) & predictably will take some time to implement, I suggest that we should discuss this further after completing other tasks & merging them. The remaining issues are what you mentioned before: that some UDT-like cells might not store their data in the first 16 bytes. It would be nice if we could make the |
/canary |
🚀 Canary version published successfully! View workflow run The following packages have been published to npm:
|
Note for future readers: in my opinion this PR should not be merged as it stands. This PR has the following issues:
That's why I proposed another WIP PR, which addresses the aforementioned issues.
I already proposed, I will review here 🤗
Yep, wouldn't it make more sense to merge those separately?
The more work we put towards them, the more they are difficult to remove later on. Right? For example, what if a non compatible SSRI-alike comes later on? And then another and another
Yep, you may want to take a look at how About the integration of this class in iCKB, as I was mentioning some time ago:
That said, currently I'm puzzled by the lack of a method to get the UDT value of a single input/output Cell. How do you propose to address this issue @Hanssen0? Love & Peace, Phroi %85 |
Agree. I should divide this PR into smaller pieces so that we can merge the less controversial content.
Yes. But I would say this PR is not making UDT more SSRI - it's already coupled with SSRI. We can still do some optimization on top of this, as long as it doesn't make things worse.
Now I see more clearly about why |
Most of them are not issues of this PR, but rather issues that existed in the current codebase, and should not be expected to be all solved once this PR is merged. |
Good work there, I review them and they are perfect!! Phroi %63 |
Awesome! I think now we can begin to break this PR into smaller pieces. Here's my plan:
This PR will focus on the first stage. After we finish that, we can have new PRs for the following. How do you think? |
be51a53
to
db757d7
Compare
So long we are able to address, some of those issues I'm happy. Let's go with the plan!! I reviewed the current state of UDT and it is reasonable, just surprised to see Phroi %19 |
Looks good, in line with phase 1 expectations, feel free to merge! 👍 Let's move to phase 2! Phroi %4 |
You suggested that before, and I'm happy to add that. If devs don't need to maintain them manually, it does help. I've trimmed down a little bit of the code and removed the For the
for normal cases. Only use
if the above methods can't satisfy what they need. |
GPT-4o says:
I'm really sorry, no disrespect intended, my English has room for improvement too 🤗
Everything seems reasonable, I meant exactly that about Just one thing that makes me wonder is I know that it's tightly coupled with tx.completeInputs Maybe it's not necessary for it to be extendable, cause maybe we don't want to automatically add non-UDT cells Maybe a second filter or something for other cells types will be needed by subclasses and that's about it Phroi %17 |
Thank you so much for the notice! I need more practice for my English (& Grammarly helped me a lot).
I'm unsure about the scenario here. You mean maybe we will need multiple filters? Or may we need more criteria in the filter? If it's the first one, will having multiple |
For #227