Skip to content
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

Cw2 migrate spec #34

Merged
merged 6 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ workflows:
- contract_cw20_base
- contract_cw20_escrow
- package_cw1
- package_cw2
- package_cw20
- lint

Expand Down Expand Up @@ -176,10 +177,10 @@ jobs:
- target
key: cargocache-cw20-escrow-rust:1.44.1-{{ checksum "~/project/Cargo.lock" }}

package_cw20:
package_cw1:
docker:
- image: rust:1.44.1
working_directory: ~/project/packages/cw20
working_directory: ~/project/packages/cw1
steps:
- checkout:
path: ~/project
Expand All @@ -188,7 +189,7 @@ jobs:
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
- restore_cache:
keys:
- cargocache-v2-cw20:1.44.1-{{ checksum "~/project/Cargo.lock" }}
- cargocache-v2-cw1:1.44.1-{{ checksum "~/project/Cargo.lock" }}
- run:
name: Add wasm32 target
command: rustup target add wasm32-unknown-unknown && rustup target list --installed
Expand Down Expand Up @@ -217,12 +218,12 @@ jobs:
paths:
- /usr/local/cargo/registry
- target
key: cargocache-v2-cw20:1.44.1-{{ checksum "~/project/Cargo.lock" }}
key: cargocache-v2-cw1:1.44.1-{{ checksum "~/project/Cargo.lock" }}

package_cw1:
package_cw2:
docker:
- image: rust:1.44.1
working_directory: ~/project/packages/cw1
working_directory: ~/project/packages/cw2
steps:
- checkout:
path: ~/project
Expand All @@ -231,7 +232,39 @@ jobs:
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
- restore_cache:
keys:
- cargocache-v2-cw1:1.44.1-{{ checksum "~/project/Cargo.lock" }}
- cargocache-v2-cw2:1.44.1-{{ checksum "~/project/Cargo.lock" }}
- run:
name: Add wasm32 target
command: rustup target add wasm32-unknown-unknown && rustup target list --installed
- run:
name: Build library for native target
command: cargo build --locked
- run:
name: Build library for wasm target
command: cargo wasm --locked
- run:
name: Run unit tests
command: cargo test --locked
# note: there are no schemas to generate
- save_cache:
paths:
- /usr/local/cargo/registry
- target
key: cargocache-v2-cw2:1.44.1-{{ checksum "~/project/Cargo.lock" }}

package_cw20:
docker:
- image: rust:1.44.1
working_directory: ~/project/packages/cw20
steps:
- checkout:
path: ~/project
- run:
name: Version information
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
- restore_cache:
keys:
- cargocache-v2-cw20:1.44.1-{{ checksum "~/project/Cargo.lock" }}
- run:
name: Add wasm32 target
command: rustup target add wasm32-unknown-unknown && rustup target list --installed
Expand Down Expand Up @@ -260,7 +293,7 @@ jobs:
paths:
- /usr/local/cargo/registry
- target
key: cargocache-v2-cw1:1.44.1-{{ checksum "~/project/Cargo.lock" }}
key: cargocache-v2-cw20:1.44.1-{{ checksum "~/project/Cargo.lock" }}

lint:
docker:
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/cw2/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
13 changes: 13 additions & 0 deletions packages/cw2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "cw2"
version = "0.1.0"
authors = ["Ethan Frey <[email protected]>"]
edition = "2018"
description = "Definition and types for the CosmWasm-2 interface"
license = "Apache-2.0"

[dependencies]
cosmwasm-std = { version = "0.10.0" }
cosmwasm-storage = { version = "0.10.0" }
schemars = "0.7"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
14 changes: 14 additions & 0 deletions packages/cw2/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CW2: A CosmWasm spec for migration info
Copyright (C) 2020 Confio OÜ

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
57 changes: 57 additions & 0 deletions packages/cw2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CW2 Spec: Contract Info for Migration

Most of the CW* specs are focused on the *public interfaces*
of the contract. The APIs used for `HandleMsg` or `QueryMsg`.
However, when we wish to migrate from contract A to contract B,
contract B needs to be aware somehow of how the *state was encoded*.

Generally we use Singletons and Buckets to store the state, but
if I upgrade to a `cw20-with-bonding-curve` contract, it will only
work properly if I am migrating from a `cw20-base` contract. But how
can the new contract know what format the data was stored.

This is where CW2 comes in. It specifies on special Singleton to
be stored on disk by all contracts on `init`. When the `migrate`
function is called, then the new contract can read that data and
see if this is an expected contract we can migrate from. And also
contain extra version information if we support multiple migrate
paths.

### Data structures

**Required**

All CW2-compliant contracts must store the following data:

* key: `\x00\x0dcontract_info` (length prefixed "contract_info" using Singleton pattern)
* data: Json-serialized `ContractVersion`

```rust
pub struct ContractVersion {
/// contract is a globally unique identifier for the contract.
/// it should build off standard namespacing for whichever language it is in,
/// and prefix it with the registry we use.
/// For rust we prefix with `crates.io:`, to give us eg. `crates.io:cw20-base`
pub contract: String,
/// version is any string that this implementation knows. It may be simple counter "1", "2".
/// or semantic version on release tags "v0.6.2", or some custom feature flag list.
/// the only code that needs to understand the version parsing is code that knows how to
/// migrate from the given contract (and is tied to it's implementation somehow)
pub version: String,
}
```

Thus, an serialized example may looks like:

```json
{
"contract": "crates.io:cw20-base",
"version": "v0.1.0"
}
```

### Queries

Since the state is well defined, we do not need to support any "smart queries".
We do provide a helper to construct a "raw query" to read the ContractInfo
of any CW2-compliant contract.
71 changes: 71 additions & 0 deletions packages/cw2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::{
HumanAddr, Querier, QueryRequest, ReadonlyStorage, StdResult, Storage, WasmQuery,
};
use cosmwasm_storage::{to_length_prefixed, ReadonlySingleton, Singleton};

pub const PREFIX_INFO: &[u8] = b"contract_info";

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct ContractVersion {
/// contract is the crate name of the implementing contract, eg. `crate:cw20-base`
/// we will use other prefixes for other languages, and their standard global namespacing
pub contract: String,
/// version is any string that this implementation knows. It may be simple counter "1", "2".
/// or semantic version on release tags "v0.6.2", or some custom feature flag list.
/// the only code that needs to understand the version parsing is code that knows how to
/// migrate from the given contract (and is tied to it's implementation somehow)
pub version: String,
}

/// get_contract_info can be use in migrate to read the previous version of this contract
pub fn get_contract_info<S: ReadonlyStorage>(storage: &S) -> StdResult<ContractVersion> {
ReadonlySingleton::new(storage, PREFIX_INFO).load()
}

/// set_contract_info should be used in init to store the original version, and after a successful
/// migrate to update it
pub fn set_contract_info<S: Storage>(storage: &mut S, info: &ContractVersion) -> StdResult<()> {
Singleton::new(storage, PREFIX_INFO).save(info)
}

/// This will make a raw_query to another contract to determine the current version it
/// claims to be. This should not be trusted, but could be used as a quick filter
/// if the other contract exists and claims to be a cw20-base contract for example.
/// (Note: you usually want to require *interfaces* not *implementations* of the
/// contracts you compose with, so be careful of overuse)
pub fn query_contract_info<Q: Querier, T: Into<HumanAddr>>(
querier: &Q,
contract_addr: T,
) -> StdResult<ContractVersion> {
let req = QueryRequest::Wasm(WasmQuery::Raw {
contract_addr: contract_addr.into(),
key: to_length_prefixed(PREFIX_INFO).into(),
});
querier.query(&req)
}

#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::MockStorage;

#[test]
fn get_and_set_work() {
let mut store = MockStorage::new();

// error if not set
assert!(get_contract_info(&store).is_err());

// set and get
let info = ContractVersion {
contract: "crate:cw20-base".to_string(),
version: "v0.1.0".to_string(),
};
set_contract_info(&mut store, &info).unwrap();
let loaded = get_contract_info(&store).unwrap();
assert_eq!(info, loaded);
}
}