-
Notifications
You must be signed in to change notification settings - Fork 397
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: lock token transfer and parameter module #3176
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is 📢 Thoughts on this report? Let us know! |
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.
Thank you for pushing this out 🙏
I think the direction is good, but I'd love to discuss more about some implementation details. I'm mostly worried about us having a temporary implementation detail as a permanent state of a core object (check comments).
Pinging @moul to give a review as well.
Please check the CI 🙏
🛠 PR Checks SummaryAll Automated Checks passed. ✅ Manual Checks (for Reviewers):
Read More🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers. ✅ Automated Checks (for Contributors):🟢 Maintainers must be able to edit this pull request (more info) ☑️ Contributor Actions:
☑️ Reviewer Actions:
📚 Resources:Debug
|
I was asked by Manfred to provide some feedback on the two different approaches. I don't have strong opinions one way or the other, here's what I consider:
If I was forced to choose, the "generic" approach seems to me simple to understand and to build on top of, and like I would shoot myself less frequently in the foot. But I'm not heavily swinging either way. |
@thehowl @moul The issue is not limited to key name and type validation. Allowing the creation and update of arbitrary chain parameters is unsafe and could lead to undetectable mistakes, potentially leaving the chain vulnerable to exploitation later. It's like using a map when we should be using a struct to pass parameters to the critical sections of a program. Here are some specifics:
In practice, proposals like this are difficult to verify in terms of their behavior and consequences because the behavior is implemented in the code itself.
Configurations can easily become out of sync with the code implementation without being noticed. This makes it challenging to track how these settings are linked in the code and how they impact chain behavior. This issue is especially problematic when we need to support backward-compatible features.
This constant does not match the genesis configuration, meaning changes in the genesis will never update the value of chainDomainParamPath as intended. gno/gno.land/genesis/genesis_params.toml Line 10 in 3fd5571
gno/gno.land/pkg/sdk/vm/params.go Line 7 in 3fd5571
It could get worse. Imagine if this property were created by GovDao instead of the genesis. All these small mistakes would be extremely difficult to detect. The old key could remain in the chain even after creating a new key |
I'll try to sum up my primary concerns with this PR, and we just need to agree if we're okay with this at this point in the chain's lifecycle: Things that keep me worried about the implementation:
What I like about the PR:
In terms of which approach to go with, maybe we can opt for a fusion of the two ideas?
I'm more inclined to opt for the field approach Manfred initially advocated for, because it seems more durable and future-proof. We already support most field types, so I can't foresee an issue when we need to change values or update structs (completely revamp what "params" are). We also don't encounter the |
This PR supports both individual field parameters and struct types. The most important gap we aim to resolve between this PR and the existing params implementation is the following: a) The current implementation treats the Param module as a native store, allowing arbitrary chain parameter key-value pairs to be added from both genesis and GovDao. b) This PR predefines chain parameters in the code, allowing only their values to be modified from genesis and GovDao. |
…o feat_lock_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.
Went through the PR again from scratch because of the changes, and it took me a minute 🙏
I think the PR applies most of the changes we've left in the comments. Apart from what @moul is discussing on the format, I think it looks alright, given what we want to accomplish now ahead of the launch.
Unblocking the PR 🙏
…o feat_lock_transfer
} | ||
|
||
func (prm *SDKParams) assertRealmAccess(key gstd.ParamKey) { | ||
realm := gno.ReRealmPath.FindString(key.Realm) |
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.
MathString? realm isn't used so more efficient.
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 replace with gno.IsRealm. It's got a bit more logic.
func (pk ParamKey) String() string { | ||
pks := "" | ||
if pk.Prefix == "" { | ||
pks = fmt.Sprintf("vm:%s.%s", pk.Realm, pk.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.
it should be fmt.Sprintf("%s:%s.%s", pk.Module, pk.Realm, pk.Key).
"vm:" should not be hard coded here so different modules can have "realms" too.
type ParamKey struct {
Module string // NOTE comes first
Realm string
Key string
Type string
}
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.
If 'auth:gno.land/r/sys/params.tx_size_cost_per_byte.string' is considered a valid key, how can we validate a user's intention to modify either a predefined module parameter (such as 'auth:max_memo_bytes' or 'auth:tx_size_cost_per_byte') or 'gno.land/r/sys/params.tx_size_cost_per_byte.string', which is not valid in the auth module?
gno/tm2/pkg/sdk/auth/params.go
Line 26 in 955e1ca
type Params struct { |
Does it also conflict with an earlier defined rule that everything modified from the contract must start with "vm:" ?
tm2/pkg/sdk/bank/keeper.go
Outdated
if amt.ContainOneOfDenom(toSet(rds)) { | ||
acc := bank.acck.GetAccount(ctx, addr) | ||
accr := acc.(std.AccountRestricter) | ||
if acc != nil && accr.IsRestricted() { |
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.
!accr.IsUnrestricted()
and accr.IsRestricted()
are NOT the same thing.
Please replace this with !accr.IsUnrestricted()
and remove all references of code of "IsRestricted()", because that should really be a separate flag, perhaps with a different name to avoid confusion.
Example: Normally you might have Alice, Bob, Carl, David, and say only Alice is Unrestricted.
With the transfer lock, only Alice can send, Bob, Carl, David cannot. After the transfer lock is released, everyone can send. Bob, Carl, and David are not restricted, and never were individually, and yet accr.IsRestricted() would still return true. Semantically wrong.
And if David were a bad actor, his account might be "Frozen". So accr.IsFrozen() might be true, but it's not the same flag (nor the inversion of) IsUnrestricted.
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.
agree, done.
if value != "" { // lock sending denoms | ||
bank.AddRestrictedDenoms(ctx, value.(string)) | ||
} else { // unlock sending ugnot | ||
bank.DelAllRestrictedDenoms(ctx) |
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 is weird to make setting a parameter be stateful like this.
We don't need "AddRestrictedDenoms" and "DelAllRestrictedDenoms", there is one parameter named "RestrictedDenoms", and there only need be one function named "bank.SetRestrictedDenoms(ctx, string)".
Then if value != "" {
should also disappear.
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 because the RestrictedDenom string allows multiple values. However, when we vote to add a RestrictedDenom from the contract, people do not necessarily know or need to know what has been added previously. That is why I used 'Add' instead of directly calling SetRestrictedDenoms to set entire restricted demons list from the GNO contract.
} | ||
} | ||
|
||
func (bank BankKeeper) AddRestrictedDenoms(ctx sdk.Context, restrictedDenoms ...string) { |
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.
Replace AddRestrictedDenoms/DelRestrictedDenoms/DelAllRestrictedDenoms with simply. "SetRestrictedDenoms".
https://github.com/gnolang/gno/pull/3176/files#r1962837296
} | ||
} | ||
|
||
bz, err := amino.MarshalJSON(param) |
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.
Rather than storing the whole thing under "key" as a single object {...},
every field in param should be broken up, and stored either as "module:_.key" or "module.key".
This requires reflection, like reflect.ValueOf(param).Field(i).Tag and .Name, and whatever logic amino uses to derive the json key name.
Context for Locking Token Transfer
This feature allows us to lock token transfers, except for paying gas fees to add a package or call a contract. The restriction will be unlocked through GovDAO voting on a proposal.
We also want a few whitelisted, unrestricted accounts to vote on the proposal since a separate fee is required to initiate and vote on proposals.
This implementation manages the lock and unlock settings in r/sys/params, where we change the chain’s parameters. Calling lock or unlock will automatically submit a proposal to GovDAO. Once GovDAO votes and approves the proposal, the token
transfer restriction will be removed instantly.
All changes to parameters specified in r/sys/params must go through GovDAO voting.
Here are some implementation details
Main Idea Behind the Alternative Approach To implement parameter module ( Discussion)
Todo: update other params...
Contributors' checklist...