-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Optimize APT FA transactions - gas charging and transfering #13194
Conversation
⏱️ 6h 37m total CI duration on this PR
🚨 2 jobs on the last run were significantly faster/slower than expected
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #13194 +/- ##
=========================================
- Coverage 33.1% 33.1% -0.1%
=========================================
Files 1753 1753
Lines 337763 337795 +32
=========================================
- Hits 111995 111964 -31
- Misses 225768 225831 +63 ☔ View full report in Codecov by Sentry. |
@@ -478,6 +478,20 @@ module aptos_framework::coin { | |||
(option::extract(burn_ref_opt), BurnRefReceipt { metadata }) | |||
} | |||
|
|||
// Permanently convert to BurnRef, and take it from the pairing. | |||
// (i.e. future calls to borrow/convert BurnRef will fail) | |||
public fun convert_and_take_paired_burn_ref<CoinType>( |
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.
cc @lightmark. This might not make sense here before the end of the migration, as taking BurnRef means that the conversion for migration no longer works
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.
conversion doesn't use BurnRef, but uses fungible_asset::burn_internal
This prevents calling get_paired_burn_ref afterwards. I am not sure if we can clone BurnRef somehow, and return it and keep it in pairing as well.
Also I am not sure if caller can have multiple BurnCapability objects, that each needs to be converted.
@lightmark , thoughts?
For APT, we call this only after features::operations_default_to_fa_apt_store() flag has been set, which will not be set until there are no more APT CoinStores
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.
Ref
s do not allow clone/copy, so that why we use hot potato pattern here. As @igor-aptos said, the conversion does not need this for better perf so you can take Refs out anytime. The issue is this is a one-time call since there is only one copy of each ref but multiple for caps.
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.
@lightmark does framework have multiple caps? If so - how are we going to convert them to Refs? do we need to actually clone/copy here with some friend function?
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 two mint caps...lemme think about it.
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 cannot generate refs... mint cap should be stored in stake.move and burn is in transaction_fee.move ( transaction_fee is a friend of stake)
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 cool, so for this PR we are good (it is burn ref we are touching).
for transforming mint ref, we need to consolidate transaction_fee and stake modules
store_exists_inline(store) | ||
} | ||
|
||
fun store_exists_inline(store: address): bool { |
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 change?
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.
argh it's missing inline, but intention was for cheaper (and lower) gas costs
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.
To save one function call? I'm not sure if it's worth making the code longer
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.
store_exists_inline is a single operation - so inling leaves the byte representation the same. If you prefer - I could just not use store_exists in this file and do "exists<>" itself throughout.
} | ||
} | ||
|
||
inline fun apt_ensure_primary_store_exists( |
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 is this inline? A long inline function used multiple times = bloating up the bytecode
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 made not huge private functions inline, to make things faster/cheaper, and was suggested to inline more things on other PRs.
What's the threshold for "function being too long for inline" in your view? here it is like if + 4 function calls, and below one is if + 2 function calls
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.
bloating up the bytecode
@movekevin why is this a problem? The Aptos Framework does not face the same package size constraints as dApps, so what is the concern with trading off more bytecode size for less execution gas?
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 not but I'd rather not bloat the bytecode if possible. Also people look at the framework for examples
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.
bloat the bytecode
@movekevin it it appropriate to consider increased bytecode size "bloat" if the bytecode size is a function of reducing gas size?
What is the problem with people looking at the framework for an example of gas-optimized code, especially for critical execution paths?
You can always add in a func comment explaining that things are inlined for the sake of performance
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.
and in general - inline is always done for performance, there is no other reasons to inline.
if (!primary_store_exists(owner, metadata)) { | ||
let store_addr = primary_store_address(owner, metadata); | ||
if (fungible_asset::store_exists(store_addr)) { | ||
object::address_to_object<FungibleStore>(store_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.
nit: < FungibleStore > is not needed
@@ -132,6 +147,31 @@ module aptos_framework::primary_fungible_store { | |||
} | |||
} | |||
|
|||
inline fun apt_store_address(account: address): 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.
Same question here - why is this inline?
} | ||
} | ||
|
||
public(friend) fun is_apt_balance_at_least(account: address, amount: u64): bool { |
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.
Doesn't feel like any apt-specific logic should be in PFS as it should remain generic. Can this be in aptos_account?
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 are specializing APT PFS to be more efficient, so these are all variants of the same functions defined in this file. (so is easier to look and see)
but I can create aptos_primary_fungible_store.move if you prefer to move them out
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.
moves to aptos_primary_fungible_store.move.
let me know if apt_primary_fungible_store name (i.e. aptos -> apt) makes more sense
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 apt makes more sense
c5ab597
to
3c74ed1
Compare
// use internal APIs, as they skip: | ||
// - owner, frozen and dispatchable checks | ||
// as APT cannot be frozen or have dispatch, and PFS cannot be transfered | ||
// (PFS could potentially be burned. regular transfer would permanently unburn the store. | ||
// Ignoring the check here has the equivalent of unburning, transfers, and then burning again) |
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.
to reviewers - want to call this out, as this biggest not-purely-optimization change, but a logical change in which invariants are checked.
chatted with @movekevin offline - will measure what inline brings, but if it has measurable perf improvement, there are no concerns of using inlining for these short functions. |
6d29055
to
dcc10e9
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.
Object is really annoying sometimes
} | ||
} | ||
|
||
public entry fun transfer( |
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.
My hunch is we should make all the functions in this module public(friend) for internal use only.
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 should you transfer APT efficiently without wanting to create full account?
only aptos_account::transfer and primary_fungible_store::transfer are options, first one creates account, second one is much less performant.
coin::register<AptosCoin>(&signer); | ||
let account_signer = account::create_account(auth_key); | ||
if (features::new_accounts_default_to_fa_apt_store_enabled()) { | ||
apt_primary_fungible_store::ensure_primary_store_exists(signer::address_of(&account_signer)); |
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 line.
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.
your coin.move functions, to keep same invariants, fail if target doesn't have neither coin store nor PFS
so this is required, if people use coin module to transfer APT
basically after I create an account, I should be able to receive APT
@@ -478,6 +478,20 @@ module aptos_framework::coin { | |||
(option::extract(burn_ref_opt), BurnRefReceipt { metadata }) | |||
} | |||
|
|||
// Permanently convert to BurnRef, and take it from the pairing. | |||
// (i.e. future calls to borrow/convert BurnRef will fail) | |||
public fun convert_and_take_paired_burn_ref<CoinType>( |
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.
Ref
s do not allow clone/copy, so that why we use hot potato pattern here. As @igor-aptos said, the conversion does not need this for better perf so you can take Refs out anytime. The issue is this is a one-time call since there is only one copy of each ref but multiple for caps.
&borrow_global<AptosCoinCapabilities>(@aptos_framework).burn_cap, | ||
); | ||
public(friend) fun burn_fee(account: address, fee: u64) acquires AptosFABurnCapabilities, AptosCoinCapabilities { | ||
if (exists<AptosFABurnCapabilities>(@aptos_framework)) { |
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.
after migration is done, right?
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.
AptosFABurnCapabilities can be created only after migration is done.
if AptosFABurnCapabilities exists, other one doesn't, and so this is the only way to charge gas from that point
let (burn_ref, burn_receipt) = coin::get_paired_burn_ref(burn_cap); | ||
apt_primary_fungible_store::burn_from(&burn_ref, account, fee); | ||
coin::return_paired_burn_ref(burn_ref, burn_receipt); |
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.
perf concering?
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 be slow, but we should convert to AptosFABurnCapabilities after nobody has CoinStore, and then this path will not be executed
1d511ec
to
5285f5e
Compare
ae06727
to
b2a904d
Compare
b2a904d
to
4a19bb1
Compare
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.
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.
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.
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.
9ae9494
to
305937c
Compare
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
|
Description
Type of Change
Which Components or Systems Does This Change Impact?
How Has This Been Tested?
Key Areas to Review
Checklist