-
Notifications
You must be signed in to change notification settings - Fork 60
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: add assets to a note #614
Conversation
Have you considered also doing #563 ? Another alternative would be to give an asset counter and a list of assets. I'm not sure how to provide the list of assets though, AFAIK we can only have 16 elements in the stack, so it would need to go somewhere else, one possibility is to push them to the advice provider and then give the hash of the assets to the |
That is an excellent point; maybe I should rather work on #563. One problem is that That means, instead of providing the full But the overall complexity grows quite a bit. I mean, you need to define before the transaction which assets you want to send with which note. That has implications for the
|
that way we could address #563 #228 and #596. That might be the easiest to simply redefine:
That might be quite a change. Let me check. |
1c18db8
to
380f834
Compare
65fb04c
to
c1b57df
Compare
c1b57df
to
3c5727c
Compare
3c5727c
to
1fabcc9
Compare
miden-lib/asm/miden/tx.masm
Outdated
#! | ||
#! ptr is the pointer to the memory address at which the note is stored. | ||
#! ASSET can be a fungible or non-fungible asset. | ||
export.add_asset_to_note |
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.
@bobbinth @hackaugusto actually here, we want to use
exec.account::remove_asset
to remove the asset before we put it on the note.
Right now, this is blocked by our MockDataStore
which is the basis for all our tests.
We need to refactor that first, before we can make create_note
cleaner.
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 a question for @bobbinth . I don't know why create_note
doesn't call remove_asset
. It seems the user is expected to manually handle these details.
// OUTPUT NOTE BUILDER | ||
// ================================================================================================ | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct OutputNoteData { |
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.
Not sure if this is the most elegant solution. Maybe a normal dict would have sufficed?
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.
What do you mean? Why do you want to use a map here?
@@ -305,6 +314,50 @@ impl<A: AdviceProvider> TransactionHost<A> { | |||
Ok(process.get_mem_value(process.ctx(), note_address).map(NoteId::from)) | |||
} | |||
} | |||
|
|||
/// ToDo: pretty ugly function, refactor |
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 works, but is pretty ugly.
# prepare the stack for return | ||
#padw push.0 push.0 movup.6 | ||
# => [ptr, 0, 0, 0, 0, 0, 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 think we have issues with the stack handling in this file.
Procedures inside the asm/kernels
folder are what form the kernel interface. This means the procedures in this file are sycall
ed (create_note
is called here ). Because this procedure is used via a syscall
, there are exactly 16 elements in the stack as inputs, and there must be exactly 16 elements in the stack as outputs.
When entering this procedure, the stack looks like this:
[tag, aux, note_type, RECIPIENT, x, X, X]
Where x, X, X
are 9 elements which may be important for the caller, so we don't want to lose them, or change their position.
Without padding the will look like this after the exec.tx::create_note
[ptr, x, X, X, 0, 0, ZERO]
The 0, 0, ZERO
at the end are automatically added by the VM, because the stack always has a minimum of 16 elements. Note that the position of the x, X, X
changed, that is what we want to prevent. One solution is just to move the elements back to its original position, something like:
movup.10 movup.11
# => [0, 0, ptr, x, X, X, ZERO]
swapw.2 swapw.3
# => [ZERO, X, 0, 0, ptr, x, X]
swapw swapw.2
# => [0, 0, ptr, x, ZERO, X, X]
movup.3 swap.7
# => [0, 0, ptr, ZERO, x, X, X]
movup.2
# => [ptr, 0, 0, ZERO, x, X, X]
Or, another alternative is to pad the with 0, 0, ZERO
before doing exec.tx::create_note
, that would be something like:
exec.authenticate_account_origin
# => [tag, aux, note_type, RECIPIENT, x, X, X]
push.0 padw
# => [ZERO, 0, tag, aux, note_type, RECIPIENT, x, X, X]
swapw.2
# => [0, tag, aux, note_type, RECIPIENT, ZERO, x, X, X]
movdn.7 push.0 movdn.7
# => [tag, aux, note_type, RECIPIENT, 0, 0, ZERO, x, X, X]
So after the exec.tx::create_note
the stack will look like:
# => [ptr, 0, 0, ZERO, x, X, X]
I guess we don't have any tests that call create_note
and check the stack elements that would be x, X, X
, like we talked 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.
Thanks for the in-depth explanation.
I changed the asm/api.masm
create_note
procedure now to
export.create_note
# authenticate that the procedure invocation originates from the account context
exec.authenticate_account_origin
# => [tag, aux, note_type, RECIPIENT]
exec.tx::create_note
# => [ptr]
# prepare the stack for return
movupw.3 movup.15 movup.15 movup.6
# => [ptr, 0, 0, 0, 0, 0, 0]
end
This should do the job.
When entering the procedure, we have
[tag, aux, note_type, RECIPIENT, x, X, X]
after exec.tx::create_note
we should have (without padding)
[ptr, x, X, X, 0, 0, ZERO]
so we can do
movupw.3 movup.15 movup.15 movup.6
to reach
[ptr, 0, 0, ZERO, x, X, X]
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 think we should document the changes to the stack in the source code too. It really helps to understand the instructions movupw.3 movup.15 movup.15 movup.6
.
# => [ptr, x, X, X, 0, 0, ZERO]
movupw.3
# => [ZERO, ptr, x, X, X, 0, 0]
movup.15 movup.15
# => [0, 0, ZERO, ptr, x, X, X]
movup.6
# => [ptr, 0, 0, ZERO, x, X, X]
Really neat solution btw.
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 added some comments
Really neat solution btw
thanks, ser
# prepare stack for return | ||
#padw movup.4 | ||
# => [ptr, 0, 0, 0, 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.
Same 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.
Same here, solved by
movupw.3 movup.4
# check that amount =< max_supply - total_issuance, fails if otherwise | ||
dup.1 gte assert.err=ERR_BASIC_FUNGIBLE_MAX_SUPPLY_OVERFLOW | ||
# => [asset, tag, note_type, RECIPIENT, ...] | ||
# => [asset, tag, aux, note_type, RECIPIENT, ...] |
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.
Not introduced by you, but this logic seems duplicated with the exec.faucet::mint
below:
miden-base/miden-lib/asm/miden/kernels/tx/faucet.masm
Lines 54 to 60 in 7e1db46
# prepare stack to ensure that minting the asset will not exceed the maximum | |
dup.7 dup exec.asset::get_fungible_asset_max_amount dup.3 | |
# => [total_issuance, max_allowed_issuance, amount, amount, TOTAL_ISSUANCE, ASSET] | |
# compute difference to ensure that the total issuance will not exceed the maximum | |
sub lte assert.err=ERR_FAUCET_ISSUANCE_OVERFLOW | |
# => [amount, TOTAL_ISSUANCE, ASSET] |
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, we should be able to delete that line here. Don't you think?
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 no, I think those are two different checks.
- The first check in the contract, checks against the max_supply, set by the user at account creation.
- The second check, in
asset.masm
checks against the total max number of any assetconst.FUNGIBLE_ASSET_MAX_AMOUNT=9223372036854775807
You are right, that in theory, the first check, should ensure that the max supply provided at slot 1 in the faucet is smaller than 9223372036854775807
, which is checked at account creation. But an additional check doesn't hurt for faucets.
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.
Sounds good 👍 . Lets maybe add a comment explaining the difference?
// OUTPUT NOTE BUILDER | ||
// ================================================================================================ | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct OutputNoteData { |
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.
What do you mean? Why do you want to use a map here?
miden-lib/asm/miden/tx.masm
Outdated
#! | ||
#! ptr is the pointer to the memory address at which the note is stored. | ||
#! ASSET can be a fungible or non-fungible asset. | ||
export.add_asset_to_note |
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 a question for @bobbinth . I don't know why create_note
doesn't call remove_asset
. It seems the user is expected to manually handle these details.
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.
Looks good! Thank you! I left some comments with improvement suggestions inline. One thing I didn't really get deep into is the handling of the padding in various functions. I'll do a more thorough review of these on the next pass.
#! Distributes freshly minted fungible assets to the provided recipient. | ||
#! Inputs: [amount, tag, note_type, RECIPIENT] | ||
#! Inputs: [amount, tag, aux, note_type, RECIPIENT] | ||
#! Outputs: [note_ptr, 0, 0, 0, 0, 0, 0, 0, 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'm not sure we need to change the public interface of this file (we could pass 0 for aux
field internally).
|
||
# check that amount =< max_supply - total_issuance, fails if otherwise | ||
dup.1 gte assert.err=ERR_BASIC_FUNGIBLE_MAX_SUPPLY_OVERFLOW | ||
# => [asset, tag, note_type, RECIPIENT, ...] | ||
# => [asset, tag, aux, note_type, RECIPIENT, ...] |
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.
Should asset
here be actually amount
?
exec.tx::move_asset_to_note | ||
# => [note_ptr, ZERO, ...] |
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.
Is the returned stack here correct? I think we usually try to make sure that the number of elements on the stack doesn't change (i.e., should this not be [note_ptr, ZERO, ZERO]
?
#! Creates a new note and returns a pointer to the memory address at which the note is stored. | ||
#! | ||
#! Inputs: [ASSET, tag, note_type, RECIPIENT] | ||
#! Outputs: [ptr, 0, 0, 0, 0, 0, 0, 0, 0] | ||
#! Inputs: [tag, aux, note_type, RECIPIENT] | ||
#! Outputs: [ptr] |
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.
What is the reason for removing zeros from the returned stack? Was it a mistake that we had them here before?
#! c, If there is no identical ASSET, the procedure returns the asset_ptr of the next | ||
#! available memory location. | ||
#! | ||
#! Inputs: [ASSET, ptr, num_of_assets] |
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: I would probably rename ptr
to note_ptr
(here and in other places).
#! ASSET can be a fungible or non-fungible asset. | ||
export.move_asset_to_note |
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.
Now seeing how this works, I think add_asset_to_note
would have been the correct name.
movdn.4 exec.asset::validate_asset | ||
# => [ASSET, ptr] | ||
|
||
movdn.8 swapw padw swapw movup.12 | ||
# => [note_ptr, RECIPIENT, 0, 0, 0, 0, 0, 0, 0, 0] | ||
# check if the note has at least one ASSET already | ||
dup.4 exec.memory::get_created_note_num_assets dup movdn.6 neq.0 | ||
# => [num_of_assets>0?, ASSET, ptr, num_of_assets] | ||
|
||
if.true | ||
exec.check_if_asset_already_exists | ||
# => [asset_ptr, ASSET'', ptr, num_of_assets] | ||
else | ||
# There is no asset in the note yet, so we add the asset at the first position | ||
dup.4 exec.memory::get_created_note_asset_data_ptr | ||
# => [asset_ptr, ASSET, ptr, num_of_assets] | ||
end |
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 have probably taken a slightly different approach here:
- First, we could validate the asset (as you have now) and determine its type (fungible or non-fungible).
- Then, for non-fungible assets, I'd create a helper procedure - e.g.,
add_non_fungible_asset_to_note
. In this function, we could use simpleeqw
comparisons in a loop, and then, at the end of the loop, if nothing failed, add the asset to the note. So, the function would be relatively simple. - For fungible assets, I'd create a separate helper procedure - e.g.,
add_fungible_asset_to_note
. I'd also create two other helper procedures:- Something like
asset::get_issuer
which would return the faucet ID that issued the asset. - Something like
asset::combine_fungible_assets
which would combine two fungible assets that have the same origin. - I would then use these functions similar to how you have now.
- Something like
While this will probably result in a bit more code - I think the code would be more encapsulated and easier to follow/maintain (e.g., if the structure of assets changes, we would only need to update the relevant helper functions).
Closes #596, #228, #563.
Adding the ability to add assets to a note.
tx::create_note
and I added the proceduretx::add_asset_to_note
. That means, first a note is being created without any assets, then, assets can be added.aux_data
field totx::create_note
and subsequent changes.OutputNotes
in theHost
needed to change. We now collect all the note data during execution, because we can always add new assets, and build the notes at the end.