-
Notifications
You must be signed in to change notification settings - Fork 355
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
Cw22 add supported interface #844
base: main
Are you sure you want to change the base?
Conversation
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 useful. Nice!
I left some comments. In general if we're doing a separate spec, I'd prefer to make it independent from cw2
rather than a superset.
Hi @eledra89 @uint @ThienLK1. Since this is a new package, I think we could add more functionalities. |
also cc @shanev per our discussion on Twitter. Can't cc Larry here though |
@peara I just merge a new pull request, add more function to define and check version of supported interfaces, please take a look! |
packages/cw22/src/lib.rs
Outdated
store: &mut dyn Storage, | ||
mut supported_interfaces: Vec<ContractSupportedInterface>, | ||
) -> Result<(), StdError> { | ||
while let Some(supported_interface) = supported_interfaces.pop() { |
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.
can we use for supported_interface in supported_interfaces {}
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.
yes, the code will be similar.
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.
Argument change would force this for loop here, which is better anyway
packages/cw22/src/lib.rs
Outdated
.unwrap_or_default(); | ||
let supported_version = Version::parse(&supported_version_rs); | ||
match supported_version { | ||
Ok(ver) => Ok(VersionResponse { |
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 this is a query, I think just return true or false would be enough.
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 returns more information to the querier. Example which specifies version interface is supported, or not?
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 this function can be removed.
query_supported_interface_version
already returns the version (or missing), be DRY.
Also, we will most likely be getting this though Map.query(...)
as we remotely query a contract (don't compare with our own contracts version). Let's make the version comparison helper separate.
I would just add two functions that don't touch storage and show the semver usage, like:
// most comparisons just require a minimum - make this easy
minimum_version(version: &str, required: &str) -> bool {}
- maybe make Result<(), Error>
?
// allow more powerful query strings - ">=1.2.3,<2"
require_version(version: &str, request: &str) -> bool {}
- or Result as above
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 you go for the Result type, please define a custom Cw22Error type in the spec.
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.
@ethanfrey for the minimum_version
, you mean a helper function like this?
pub fn minimum_version(version: &str, required: &str) -> bool {
let req = VersionReq::parse(">={required}").unwrap();
let version = Version::parse(version);
req.matches(&version)
}
@uint @ethanfrey , we added cw22: supported interface with additional support for version checking. |
Nice. I took a quick look over the comments and agree with the direction. Using a separate independent interface, as well as using a Map for easier checks. I want to say that one nice part of cw2 is that it has a well-defined storage structure, so you can do "raw queries" against it if needed. Making the storage layout part of this spec also sounds like a good idea. Sure, we can do smart queries, but that depends on a higher-level API and is less efficient. A well defined storage layer allows efficient raw queries with no more code on the queried contract. We also have helpers for this, a calling contract can do something like:
I will go through briefly on the code, but this is my high-level input. |
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 number of comments on the code.
I like the direction, but please clean this up so it is up to standards to merge
packages/cw22/README.md
Outdated
* data: Json-serialized `ContractSupportedInterface` | ||
|
||
```rust | ||
pub struct ContractSupportedInterface { |
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: this is different than implementation, please update
packages/cw22/src/lib.rs
Outdated
use query::VersionResponse; | ||
use semver::{Version, VersionReq}; | ||
|
||
pub const SUPPORTED_INTERFACES: Map<String, String> = Map::new("supported_interfaces"); |
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.
Please use Map<&str, String>
, it is more efficient and easier to call
packages/cw22/src/lib.rs
Outdated
/// of supported interfaces. It should also be used after every migration. | ||
pub fn set_contract_supported_interface( | ||
store: &mut dyn Storage, | ||
mut supported_interfaces: Vec<ContractSupportedInterface>, |
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 we just use &str in the Map, this cheaply becomes: supported_interfaces: &[ContractSupportedInterface]
packages/cw22/src/lib.rs
Outdated
store: &mut dyn Storage, | ||
mut supported_interfaces: Vec<ContractSupportedInterface>, | ||
) -> Result<(), StdError> { | ||
while let Some(supported_interface) = supported_interfaces.pop() { |
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.
Argument change would force this for loop here, which is better anyway
packages/cw22/src/lib.rs
Outdated
mut supported_interfaces: Vec<ContractSupportedInterface>, | ||
) -> Result<(), StdError> { | ||
while let Some(supported_interface) = supported_interfaces.pop() { | ||
let id = supported_interface.supported_interface; |
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 just use a shorter local variable than "supported_interface" and inline this. The following should be clear enough (but change x to item or so if you prefer)
for x in supported_interfaces {
SUPPORTED_INTERFACES.save(store, &x.supported_interface, &x.version)?;
}
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.
Actually, since we assume all stored versions are semver later on in queries, enforce it here.
Parse the values as Version and error if they are invalid - much better to error on instantiate (fail fast in tests) than later when first querying in production.
packages/cw22/src/lib.rs
Outdated
pub fn query_supported_interface_version( | ||
store: &dyn Storage, | ||
interface: String, | ||
) -> StdResult<ContractSupportedInterface> { |
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 return StdResult<Option<String>>
here. Unless there is a read error, it returns None
for not registered, and the version if registered.
packages/cw22/src/lib.rs
Outdated
) -> StdResult<ContractSupportedInterface> { | ||
let version = SUPPORTED_INTERFACES | ||
.may_load(store, interface.clone())? | ||
.unwrap_or_default(); |
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 do want the difference between None and Some here (not just "")
packages/cw22/src/lib.rs
Outdated
.unwrap_or_default(); | ||
let supported_version = Version::parse(&supported_version_rs); | ||
match supported_version { | ||
Ok(ver) => Ok(VersionResponse { |
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 this function can be removed.
query_supported_interface_version
already returns the version (or missing), be DRY.
Also, we will most likely be getting this though Map.query(...)
as we remotely query a contract (don't compare with our own contracts version). Let's make the version comparison helper separate.
I would just add two functions that don't touch storage and show the semver usage, like:
// most comparisons just require a minimum - make this easy
minimum_version(version: &str, required: &str) -> bool {}
- maybe make Result<(), Error>
?
// allow more powerful query strings - ">=1.2.3,<2"
require_version(version: &str, request: &str) -> bool {}
- or Result as above
packages/cw22/src/query.rs
Outdated
use cosmwasm_schema::cw_serde; | ||
|
||
#[cw_serde] | ||
pub struct VersionResponse { |
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 remove this. I don't define smart queries for this, they require more wiring.
Let's be like cw2 (and cw4) and focus on spec'ing the storage layout for efficient queries. The contract should be able to easily write its own storage. Other contracts will be querying it's storage (Map.query) and testing the resulting value.
packages/cw22/src/lib.rs
Outdated
.unwrap_or_default(); | ||
let supported_version = Version::parse(&supported_version_rs); | ||
match supported_version { | ||
Ok(ver) => Ok(VersionResponse { |
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 you go for the Result type, please define a custom Cw22Error type in the spec.
oh wow this is super detail @ethanfrey, we are kind of new to Rust & CosmWasm so this is very helpful in the long term ! |
Would love to get this merged in @eledra89 if you get a chance to resolve the PR comments. |
@shanev thanks for reminding us. We have been pretty busy. We will fix it this week. |
@uint no longer works at Confio and I am too booked to review this well. @hashedone maybe you could recommend someone else who could review this? (Once they push the update. No rush, but it would be good to figure out who can review this) |
01d948c
to
de1fb0b
Compare
Cw22 improvement
@ethanfrey I think I will just do this one myself 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.
The idea is great, and the solution is pretty good. Still, I would strongly advise migrating from String
to Cow
- I know we do String
in most places in cw, but we already tried to use Cow
where possible in Sylvia. We should go for that in interfaces, particularly those which we want to be implemented by most of the contracts to reduce extra gas cost on those small things (I get it is only for instantiation/migration, but it is very much a free change).
@jawoznia please take a look at that - we probably will auto-support this in Sylvia in the future.
I give approval as the Cow
thing is not super-critical, but if you can please align. If not, please at least create an issue out of my comment.
packages/cw22/README.md
Outdated
/// e.g "crates.io:cw2" | ||
/// NOTE: this is just a hint for the caller to adapt on how to interact with this contract. | ||
/// There is no guarantee that the contract actually implement these interfaces. | ||
pub supported_interface: 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.
Nit:
This thing is mainly used with string literals to store it in DB. However, you always need to allocate the string for no real reason adding unnecessary cost. I understand you do it, so you can deserialize it keeping ownership.
You can avoid it with a Cow type. Just add a lifetime generic on top (ContractSupportedInterface<'a>
) and then change all strings to Cow<'a, str>
. Then instead of String::from("foo")
you use Cow::borrowed("foo")
, Cow::from("foo")
, or even "foo".into()
, and you avoid allocation unless you really need it (and you will not need it for serialization - serde handles (de)serialization through Cow
transparently).
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, even though it is not exactly the same as in implementation, I would leave it this way - not sure if you forgot to update or left it on purpose, but it is probably easier to think about this way and it is semantically equivalent.
packages/cw22/README.md
Outdated
supported_interface: String::from("crates.io:cw20"), | ||
version: String::from("0.16.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.
Yea, I may be a little bit biased but seeing this and then sending the whole structure by reference line later my whole brain screams "cost".
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.
However, those should be updated - as supported_interface
and version
are Cow
internally, this won't work. Both should be changed either to:
supported_interface: From::from("crates.io:cw20"),
version: From::from("0.16.0"),
or
supported_interface: "crates.io:cw20".into(),
version: "0.16.0".into(),
Both of those work for ContractSupportedInterface
defined as String
and as Cow
. I personally prefer the .into()
as it feels more natural to me, but I'll accept both.
Sorry for the annoying nitpicking here. We struggle with documentation quality, and we don't want to introduce any more dept in this area.
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.
supported_interface: String::from("crates.io:cw20"), | |
version: String::from("0.16.0"), | |
supported_interface: "crates.io:cw20".into(), | |
version: String::from"0.16.0".into(), |
packages/cw22/src/lib.rs
Outdated
/*! | ||
CW22 defines a way for a contract to declare which interfaces do the contract implement | ||
This standard is inspired by the EIP-165 from Ethereum. Originally it was proposed to | ||
be merged into CW2: Contract Info, then it is splitted to a separated cargo to keep CW2 | ||
being backward compatible. | ||
|
||
Each supported interface contains a string value pointing to the corresponding cargo package | ||
and a specific release of the package. There is also a function to check whether the contract | ||
support a specific version of an interface or not. | ||
|
||
The version string for each interface follows Semantic Versioning standard. More info is in: | ||
https://docs.rs/semver/latest/semver/ | ||
*/ |
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/consistency: we use ///
for doc comments everywhere and for mod-level comments, //!
- please stick to this pattern. It is more common in general in rust ecosystems.
packages/cw22/src/lib.rs
Outdated
/// e.g "crates.io:cw2" | ||
/// NOTE: this is just a hint for the caller to adapt on how to interact with this contract. | ||
/// There is no guarantee that the contract actually implement these interfaces. | ||
pub supported_interface: 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.
This is where the comment about Cow
should actually go.
@hashedone I have updated with |
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.
LGTM with minor docs comment.
packages/cw22/README.md
Outdated
/// e.g "crates.io:cw2" | ||
/// NOTE: this is just a hint for the caller to adapt on how to interact with this contract. | ||
/// There is no guarantee that the contract actually implement these interfaces. | ||
pub supported_interface: 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.
Ok, even though it is not exactly the same as in implementation, I would leave it this way - not sure if you forgot to update or left it on purpose, but it is probably easier to think about this way and it is semantically equivalent.
packages/cw22/README.md
Outdated
supported_interface: String::from("crates.io:cw20"), | ||
version: String::from("0.16.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.
However, those should be updated - as supported_interface
and version
are Cow
internally, this won't work. Both should be changed either to:
supported_interface: From::from("crates.io:cw20"),
version: From::from("0.16.0"),
or
supported_interface: "crates.io:cw20".into(),
version: "0.16.0".into(),
Both of those work for ContractSupportedInterface
defined as String
and as Cow
. I personally prefer the .into()
as it feels more natural to me, but I'll accept both.
Sorry for the annoying nitpicking here. We struggle with documentation quality, and we don't want to introduce any more dept in this area.
packages/cw22/README.md
Outdated
supported_interface: String::from("crates.io:cw20"), | ||
version: String::from("0.16.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.
supported_interface: String::from("crates.io:cw20"), | |
version: String::from("0.16.0"), | |
supported_interface: "crates.io:cw20".into(), | |
version: String::from"0.16.0".into(), |
@hashedone No problem, I actually forgot that, would have hate it myself. |
Implement #842