-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Refactor fungible assets #7379
Refactor fungible assets #7379
Conversation
7132857
to
e13fcd3
Compare
2d861e7
to
d8fc813
Compare
/// Derives an object address from type and source account: sha3_256([source | type | seed | 0xFC]). | ||
public fun create_typed_object_address<T>(source: &address, seed: vector<u8>): address { | ||
let bytes = bcs::to_bytes(source); | ||
let type_name_bytes = bcs::to_bytes(&type_info::type_name<T>()); | ||
vector::append(&mut bytes, type_name_bytes); | ||
vector::append(&mut bytes, seed); | ||
vector::push_back(&mut bytes, OBJECT_FROM_TYPE_SCHEME); | ||
from_bcs::to_address(hash::sha3_256(bytes)) | ||
} | ||
|
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.
Shouldn't seed just be the typeinfo maybe bcs serialized? I don't understand why we want a seed here.
alternatively... could we make an object witness such that I could do the following:
create_deterministic_object(user: &signer, ref: &Witness): ConstructorRef
where Witness is a ref that can be accessed either via ConstructorRef or ExtendRef
...
Then an object implementation can expose a function like:
fun generate_account_object(account: &signer): Object<T>
which would create a fully useful object of say a fungible 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.
Seed is used to create a separate but deterministic wallet address for each fungible asset metadata object.
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.
Yep, an object implementation such as fungible assets might still want to create multiple objects with the same address formula (i.e. type T). In a way, type T allows namespacing objects by module/type.
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 don't think this is particularly needed. We should already be able to create arbitrary objects using the guid or named pattern. The disadvantage of having this is is it effectively becomes a new named variant.
If someone passes in a seed, it will be untrackable without tracking all possible seeds. At which point it doesn't seem different than what we started with... requiring off-chain or on-chain coordination to determine which of possibly any object is the one we're looking for.
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.
Switched over to using a DeriveRef to generate deterministic object addresses of this form:
hash(owner addr | address of object to derive from | 0xFC) where 0xFC is a new domain separator in object.move
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
/// Get the current supply from `metadata`. | ||
public fun supply<T: key>(metadata: &Object<T>): u64 acquires FungibleAssetMetadata { | ||
borrow_fungible_metadata(metadata).supply | ||
public fun supply<T: key>(metadata: Object<T>): u64 acquires FungibleAssetMetadata { |
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.
@gerben-stavenga would add reference support soon.
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.
oh that would be nice
/// Get the maximum supply from `metadata`. | ||
public fun maximum<T: key>(metadata: &Object<T>): Option<u64> acquires FungibleAssetMetadata { | ||
borrow_fungible_metadata(metadata).maximum | ||
public fun maximum<T: key>(metadata: Object<T>): Option<u64> acquires FungibleAssetMetadata { |
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 we chatted in slack about replacing T
with FungibleAssetMetadata
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 but it's still controversial as there can be some improvements we can later on in the adapter layer that might make this reasonable. Let's discuss more in a wider setting
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.
Ahh... so this is still up for discussion.
considering that we are doing a copy on all these functions anyway (we're passing in the value), it might not be the end of the world to expect that this is coerced to the appropriate type. Perhaps this also creates the opportunity for someone to validate it is of a given type before coercing. Let them have their own error handling if they want it.
Note, we will still need to re-validate exists<T>(object_address(obj))
since an Object<T>
just implies it existed at some point.
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 this warrants a deeper discussion on whether the experience can be improved regarding using entry + view functions with Object if it's not clear by the callers what T should be. One example is the explorer's view UI where users can call the view functions of modules.
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 wonder if we can have a default placeholder for Object if called by a view or entry function... i.e., ObjectCore
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 I was thinking exactly this too. We need to think about this a bit more about how to do this though
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
inline fun assert_owner<T: key>(owner: &signer, metadata: &Object<T>) { | ||
assert!(object::is_owner(*metadata, signer::address_of(owner)), error::permission_denied(ENOT_OWNER)); | ||
/// Create a new fungible asset with a value of 0. | ||
public fun zero<T: key>(metadata: Object<T>): FungibleAsset { |
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 see similar things in current coin.move. But I don't understand the potential use case. Can you share an example in comment?
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.
In the past, this is really useful because Coin is storable. zero() would allow people to initialize their module's key resources with empty state. It's less useful now that Coin is no longer storable, but it can make the following programming pattern easier:
let all_payments = fungible_asset::zero();
collect_payment_1(&mut all_payments, ...);
collect_payment_2(&mut all_payments, ...);
collect_payment_3(&mut all_payments, ...);
What do 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.
Then I would prefer a merge_all
function in my old PR that takes a vector of fa and merge into 1 using vector::fold
. We should make the invariance that zero fa will never be produced to simplify our model
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 removed zero(). You can add merge_all in a follow up PR :)
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move
Show resolved
Hide resolved
f027e88
to
c91c148
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.
this kludges together a primary wallet from an arbitrary wallet... could we please move the primary wallet outside of this context?
it'd be great to have a withdraw that extracts from a signer and another that expects a signer and an object pointer. Similarly a deposit that points to a user's address and another that points to an object.
/// Derives an object address from type and source account: sha3_256([source | type | seed | 0xFC]). | ||
public fun create_typed_object_address<T>(source: &address, seed: vector<u8>): address { | ||
let bytes = bcs::to_bytes(source); | ||
let type_name_bytes = bcs::to_bytes(&type_info::type_name<T>()); | ||
vector::append(&mut bytes, type_name_bytes); | ||
vector::append(&mut bytes, seed); | ||
vector::push_back(&mut bytes, OBJECT_FROM_TYPE_SCHEME); | ||
from_bcs::to_address(hash::sha3_256(bytes)) | ||
} | ||
|
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 don't think this is particularly needed. We should already be able to create arbitrary objects using the guid or named pattern. The disadvantage of having this is is it effectively becomes a new named variant.
If someone passes in a seed, it will be untrackable without tracking all possible seeds. At which point it doesn't seem different than what we started with... requiring off-chain or on-chain coordination to determine which of possibly any object is the one we're looking for.
|
||
/// Emitted when fungible assets are deposited into a wallet. | ||
struct DepositEvent has drop, store { | ||
metadata: Object<FungibleAssetMetadata>, |
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 don't think we need this, the event is emitted from the associated object that can then be used to look up the metadata object. We should only add this if we don't want a layer of indirection.
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 that's what I was thinking - saving one layer of indirection. It can make the indexing flow a lot simpler. Will check with @bowenyang007 later on this
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 clean this up and challenge Bowen.
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.
Sure. I'll talk to Bowen later
/// Get the maximum supply from `metadata`. | ||
public fun maximum<T: key>(metadata: &Object<T>): Option<u64> acquires FungibleAssetMetadata { | ||
borrow_fungible_metadata(metadata).maximum | ||
public fun maximum<T: key>(metadata: Object<T>): Option<u64> acquires FungibleAssetMetadata { |
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.
Ahh... so this is still up for discussion.
considering that we are doing a copy on all these functions anyway (we're passing in the value), it might not be the end of the world to expect that this is coerced to the appropriate type. Perhaps this also creates the opportunity for someone to validate it is of a given type before coercing. Let them have their own error handling if they want it.
Note, we will still need to re-validate exists<T>(object_address(obj))
since an Object<T>
just implies it existed at some point.
} | ||
|
||
/// Withdarw `amount` of fungible metadata from `account` ignoring `allow_ungated_transfer`. | ||
/// Withdraw `amount` of fungible metadata from an account ignoring `allow_ungated_transfer`. | ||
public fun withdraw_with_ref( |
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 do you need the ref and the address?
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 ref points to the metadata obj while address is the account where the asset is withdrawn from. Address has now been updated to wallet reference (Object)
c91c148
to
f1c9e14
Compare
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-framework/sources/fungible_asset.move
Outdated
Show resolved
Hide resolved
assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO)); | ||
let wallet = borrow_fungible_asset_mut(wallet); | ||
assert!(wallet.allow_ungated_transfer, error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); | ||
let wallet = borrow_global_mut<FungibleAssetWallet>(wallet_addr); |
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.
but withdraw_with_ref
doesn't check. why not put the check here instead?
aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move
Show resolved
Hide resolved
1c9892f
to
424bb20
Compare
I've updated the implementation to do exactly this - primary wallet operations are done via primary_wallet module (name's up for negotiation) while the operations in fungible_wallet deal with wallets (Object) directly |
424bb20
to
0cfb6e7
Compare
0cfb6e7
to
fea8099
Compare
…pass Object<FungibleAssetMetadata>
f22dd39
to
c07075b
Compare
c07075b
to
dc9d6ab
Compare
ref: &TransferRef, | ||
wallet: &Object<FungibleAssetWallet>, | ||
wallet: Object<T>, |
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 this is T
?
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 as an earlier comment - I'm just keeping all functions that take Object references consistent and make them generic
/// | ||
/// This 0xFC constant serves as a domain separation tag to prevent existing authentication key and resource account | ||
/// derivation to produce an object address. | ||
const OBJECT_DERIVED_SCHEME: u8 = 0xFC; |
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.
really need a centralized place for those domain separators.
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.
Where else would they be outside of object?
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.
resource acct scheme
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.
Since constants are not public currently. We'll need to provide getter functions which is kinda annoying though
|
||
#[resource_group_member(group = aptos_framework::object::ObjectGroup)] | ||
/// Resource stored on the fungible asset metadata object to allow creating primary wallets for it. | ||
struct PrimaryWalletSupport has key { |
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.
maybe rename as DeriveRefPod
. Support as a resource looks weird.
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.
Peas in a pod? Let's rename things all in one go in a followup PR
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ Forge suite
|
✅ Forge suite
|
✅ Forge suite
|
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.
need some move-e2e-tests and maybe some api tests
missing tests for "primary wallet"
naming is hard
|
||
/// Creators of fungible assets can call this to enable support for creating primary (deterministic) wallets for | ||
/// their users. | ||
public fun enable_primary_wallet(metadata_constructor_ref: &ConstructorRef) { |
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 the plan was the have another method that would wrap FungibleAssetMetadata?
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?
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 seems what you'd really want is to use this to create the FungibleAssetMetadata
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.
Huh? So move FungibleAssetMetadata to this module?
let recipient_wallet = ensure_primary_wallet_exists(recipient, metadata); | ||
fungible_asset::transfer(sender, sender_wallet, recipient_wallet, amount); | ||
} | ||
} |
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 lack of test cases...
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 @lightmark will work on those
@@ -160,6 +188,14 @@ module aptos_framework::object { | |||
create_object_internal(creator_address, obj_addr, false) | |||
} | |||
|
|||
/// Create a new object whose address is derived based on the creator account address and another object. | |||
/// Derivde objects, similar to named objects, cannot be deleted. | |||
public fun create_derived_object(creator: &signer, derive_ref: &DeriveRef): ConstructorRef { |
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 this be user derived?
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 derived from the user addr + object addr. Do you mean something else?
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.
create_user_derived_object
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'd make this friend only for now.
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.
@davidiw , do we want users to call this to generate un-deletable objects?
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.
They can't be deleted
gah, please don't enable auto-merge in the future. |
See commits for the full list of changes.
Remaining work: