diff --git a/internal/api/bindings.h b/internal/api/bindings.h index 28bb947fc..43795a5e7 100644 --- a/internal/api/bindings.h +++ b/internal/api/bindings.h @@ -491,6 +491,19 @@ struct UnmanagedVector migrate(struct cache_t *cache, struct GasReport *gas_report, struct UnmanagedVector *error_msg); +struct UnmanagedVector migrate2(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct ByteSliceView migrate_info, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + struct UnmanagedVector sudo(struct cache_t *cache, struct ByteSliceView checksum, struct ByteSliceView env, diff --git a/internal/api/lib.go b/internal/api/lib.go index 81df86c84..4ce906a13 100644 --- a/internal/api/lib.go +++ b/internal/api/lib.go @@ -337,6 +337,51 @@ func Migrate( return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } +func Migrate2( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + migrateInfo []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + i := makeView(migrateInfo) + defer runtime.KeepAlive(i) + var pinner runtime.Pinner + pinner.Pin(gasMeter) + checkAndPinAPI(api, pinner) + checkAndPinQuerier(querier, pinner) + defer pinner.Unpin() + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.migrate2(cache.ptr, cs, e, m, i, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + func Sudo( cache Cache, checksum []byte, diff --git a/internal/api/libwasmvm.x86_64.so b/internal/api/libwasmvm.x86_64.so index a4d2d96ea..23627c3bf 100755 Binary files a/internal/api/libwasmvm.x86_64.so and b/internal/api/libwasmvm.x86_64.so differ diff --git a/lib_libwasmvm.go b/lib_libwasmvm.go index e9c160926..9d108fe8b 100644 --- a/lib_libwasmvm.go +++ b/lib_libwasmvm.go @@ -262,6 +262,50 @@ func (vm *VM) Migrate( return &result, gasReport.UsedInternally, nil } +// Migrate will migrate an existing contract to a new code binary. +// This takes storage of the data from the original contract and the Checksum of the new contract that should +// replace it. This allows it to run a migration step if needed, or return an error if unable to migrate +// the given data. +// +// MigrateMsg has some data on how to perform the migration. +// +// Migrate2 takes one more argument - `migateInfo`. It consist of an additional data +// related to the on-chain current contract's state version. +func (vm *VM) Migrate2( + checksum Checksum, + env types.Env, + migrateMsg []byte, + migrateInfo types.MigrateInfo, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.ContractResult, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + + migrateBin, err := json.Marshal(migrateInfo) + if err != nil { + return nil, 0, err + } + + data, gasReport, err := api.Migrate2(vm.cache, checksum, envBin, migrateMsg, migrateBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var result types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) + if err != nil { + return nil, gasReport.UsedInternally, err + } + return &result, gasReport.UsedInternally, nil +} + // Sudo allows native Go modules to make priviledged (sudo) calls on the contract. // The contract can expose entry points that cannot be triggered by any transaction, but only via // native Go modules, and delegate the access control to the system. diff --git a/libwasmvm/Cargo.lock b/libwasmvm/Cargo.lock index f258bacc0..f50f0032c 100644 --- a/libwasmvm/Cargo.lock +++ b/libwasmvm/Cargo.lock @@ -453,12 +453,10 @@ dependencies = [ [[package]] name = "cosmwasm-core" version = "2.1.3" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.1.3#6f61673273315029992a54817942c620dab0447a" [[package]] name = "cosmwasm-crypto" version = "2.1.3" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.1.3#6f61673273315029992a54817942c620dab0447a" dependencies = [ "ark-bls12-381", "ark-ec", @@ -480,7 +478,6 @@ dependencies = [ [[package]] name = "cosmwasm-derive" version = "2.1.3" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.1.3#6f61673273315029992a54817942c620dab0447a" dependencies = [ "proc-macro2", "quote", @@ -490,7 +487,6 @@ dependencies = [ [[package]] name = "cosmwasm-std" version = "2.1.3" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.1.3#6f61673273315029992a54817942c620dab0447a" dependencies = [ "base64 0.22.1", "bech32", @@ -501,6 +497,7 @@ dependencies = [ "derive_more", "hex", "rand_core", + "rmp-serde", "schemars", "serde", "serde-json-wasm", @@ -512,7 +509,6 @@ dependencies = [ [[package]] name = "cosmwasm-vm" version = "2.1.3" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.1.3#6f61673273315029992a54817942c620dab0447a" dependencies = [ "bech32", "bytes", diff --git a/libwasmvm/Cargo.toml b/libwasmvm/Cargo.toml index c95a18040..07f98326a 100644 --- a/libwasmvm/Cargo.toml +++ b/libwasmvm/Cargo.toml @@ -26,12 +26,14 @@ default = [] backtraces = [] [dependencies] -cosmwasm-std = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.1.3", features = [ +# TODO tkulik: UNDO +cosmwasm-std = { path = "/home/tkulik/Workspace/cosmwasm/packages/std", features = [ "staking", "stargate", "iterator", ] } -cosmwasm-vm = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.1.3", features = [ +# TODO tkulik: UNDO +cosmwasm-vm = { path = "/home/tkulik/Workspace/cosmwasm/packages/vm", features = [ "staking", "stargate", "iterator", diff --git a/libwasmvm/bindings.h b/libwasmvm/bindings.h index 28bb947fc..43795a5e7 100644 --- a/libwasmvm/bindings.h +++ b/libwasmvm/bindings.h @@ -491,6 +491,19 @@ struct UnmanagedVector migrate(struct cache_t *cache, struct GasReport *gas_report, struct UnmanagedVector *error_msg); +struct UnmanagedVector migrate2(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct ByteSliceView migrate_info, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + struct UnmanagedVector sudo(struct cache_t *cache, struct ByteSliceView checksum, struct ByteSliceView env, diff --git a/libwasmvm/src/calls.rs b/libwasmvm/src/calls.rs index 38d8fbf99..841ebd4b9 100644 --- a/libwasmvm/src/calls.rs +++ b/libwasmvm/src/calls.rs @@ -10,7 +10,7 @@ use cosmwasm_vm::{ call_execute_raw, call_ibc_channel_close_raw, call_ibc_channel_connect_raw, call_ibc_channel_open_raw, call_ibc_destination_callback_raw, call_ibc_packet_ack_raw, call_ibc_packet_receive_raw, call_ibc_packet_timeout_raw, call_ibc_source_callback_raw, - call_instantiate_raw, call_migrate_raw, call_query_raw, call_reply_raw, call_sudo_raw, Backend, + call_instantiate_raw, call_migrate_raw, call_migrate2_raw, call_query_raw, call_reply_raw, call_sudo_raw, Backend, Cache, Instance, InstanceOptions, VmResult, }; @@ -126,6 +126,38 @@ pub extern "C" fn migrate( ) } +#[no_mangle] +pub extern "C" fn migrate2( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + migrate_info: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_3_args( + call_migrate2_raw, + cache, + checksum, + env, + msg, + migrate_info, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + #[no_mangle] pub extern "C" fn sudo( cache: *mut cache_t, diff --git a/types/types.go b/types/types.go index 717625c0c..0c7f3f829 100644 --- a/types/types.go +++ b/types/types.go @@ -218,6 +218,20 @@ func (pm *PinnedMetrics) UnmarshalMessagePack(data []byte) error { // never result in a "None" value on the Rust side, making the "Option" pointless. type Array[C any] []C +// The structure contains additional information related to the +// contract's state migration procedure - the sender address and +// the contract's state version currently stored on the blockchain. +// The `old_state_version` is optional, since there is no guarantee +// that the currently stored contract's binary contains that information. +type MigrateInfo struct { + // Bech32 encoded sdk.AccAddress of the contract, to be used when sending messages + Sender HumanAddress `json:"sender"` + // Version of the previous contract's state. It's optional, since + // adding the state's version number to the binary is not a + // mandatory feature. + OldStateVersion *uint64 `json:"old_state_version"` +} + // MarshalJSON ensures that we get "[]" for nil arrays func (a Array[C]) MarshalJSON() ([]byte, error) { if len(a) == 0 {