Implement improvements to new address generation (#1014)

* Revert default instance address generation to classic sequence based method

 Please enter the commit message for your changes. Lines starting

* Start re-adding classic address generator

* Extract address generation to file; minor updates

* Review comments

* Set max salt size

* Support predictable address on instantiation

* Switch attribute order for backwards compatiblity

* Fix salt param check in CLI

* Enable tests

* Add more tests

* Minor fix

* Remove migration

* Better cli description

* Fix init message length prefix

* Add sanity checks to address generation and minor updates

* Reduce max length in tests for CI

* CLI and address generation updates

* Add test vectors

* Minor updates

* Fix cli long doc
This commit is contained in:
Alexander Peters
2022-09-22 18:22:35 +02:00
committed by GitHub
parent 54fec05c11
commit 9c5ebbbc4c
36 changed files with 2246 additions and 527 deletions

View File

@@ -1,5 +1,5 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail
set -o errexit -o nounset -o pipefail -x
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
@@ -9,7 +9,10 @@ RESP=$(wasmd tx wasm store "$DIR/../../x/wasm/keeper/testdata/hackatom.wasm" \
--from validator --gas 1500000 -y --chain-id=testing --node=http://localhost:26657 -b block -o json)
CODE_ID=$(echo "$RESP" | jq -r '.logs[0].events[1].attributes[-1].value')
CODE_HASH=$(echo "$RESP" | jq -r '.logs[0].events[1].attributes[-2].value')
echo "* Code id: $CODE_ID"
echo "* Code checksum: $CODE_HASH"
echo "* Download code"
TMPDIR=$(mktemp -t wasmdXXXXXX)
wasmd q wasm code "$CODE_ID" "$TMPDIR"
@@ -27,6 +30,17 @@ wasmd tx wasm instantiate "$CODE_ID" "$INIT" --admin="$(wasmd keys show validato
CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" -o json | jq -r '.contracts[-1]')
echo "* Contract address: $CONTRACT"
echo "## Create new contract instance with predictable address"
wasmd tx wasm instantiate2 "$CODE_ID" "$INIT" $(echo -n "testing" | xxd -ps) \
--admin="$(wasmd keys show validator -a)" \
--from validator --amount="100ustake" --label "local0.1.0" \
--fix-msg \
--gas 1000000 -y --chain-id=testing -b block -o json | jq
predictedAdress=$(wasmd q wasm build-address "$CODE_HASH" $(wasmd keys show validator -a) $(echo -n "testing" | xxd -ps) "$INIT")
wasmd q wasm contract "$predictedAdress" -o json | jq
echo "### Query all"
RESP=$(wasmd query wasm contract-state all "$CONTRACT" -o json)
echo "$RESP" | jq

View File

@@ -23,6 +23,8 @@
- [MsgExecuteContract](#cosmwasm.wasm.v1.MsgExecuteContract)
- [MsgExecuteContractResponse](#cosmwasm.wasm.v1.MsgExecuteContractResponse)
- [MsgInstantiateContract](#cosmwasm.wasm.v1.MsgInstantiateContract)
- [MsgInstantiateContract2](#cosmwasm.wasm.v1.MsgInstantiateContract2)
- [MsgInstantiateContract2Response](#cosmwasm.wasm.v1.MsgInstantiateContract2Response)
- [MsgInstantiateContractResponse](#cosmwasm.wasm.v1.MsgInstantiateContractResponse)
- [MsgMigrateContract](#cosmwasm.wasm.v1.MsgMigrateContract)
- [MsgMigrateContractResponse](#cosmwasm.wasm.v1.MsgMigrateContractResponse)
@@ -327,7 +329,7 @@ MsgExecuteContractResponse returns execution result data.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `data` | [bytes](#bytes) | | Data contains base64-encoded bytes to returned from the contract |
| `data` | [bytes](#bytes) | | Data contains bytes to returned from the contract |
@@ -355,6 +357,45 @@ code id.
<a name="cosmwasm.wasm.v1.MsgInstantiateContract2"></a>
### MsgInstantiateContract2
MsgInstantiateContract2 create a new smart contract instance for the given
code id with a predicable address.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `sender` | [string](#string) | | Sender is the that actor that signed the messages |
| `admin` | [string](#string) | | Admin is an optional address that can execute migrations |
| `code_id` | [uint64](#uint64) | | CodeID is the reference to the stored WASM code |
| `label` | [string](#string) | | Label is optional metadata to be stored with a contract instance. |
| `msg` | [bytes](#bytes) | | Msg json encoded message to be passed to the contract on instantiation |
| `funds` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | Funds coins that are transferred to the contract on instantiation |
| `salt` | [bytes](#bytes) | | Salt is an arbitrary value provided by the sender. Size can be 1 to 64. |
| `fix_msg` | [bool](#bool) | | FixMsg include the msg value into the hash for the predictable address. Default is false |
<a name="cosmwasm.wasm.v1.MsgInstantiateContract2Response"></a>
### MsgInstantiateContract2Response
MsgInstantiateContract2Response return instantiation result data
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | Address is the bech32 address of the new contract instance. |
| `data` | [bytes](#bytes) | | Data contains bytes to returned from the contract |
<a name="cosmwasm.wasm.v1.MsgInstantiateContractResponse"></a>
### MsgInstantiateContractResponse
@@ -364,7 +405,7 @@ MsgInstantiateContractResponse return instantiation result data
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | Address is the bech32 address of the new contract instance. |
| `data` | [bytes](#bytes) | | Data contains base64-encoded bytes to returned from the contract |
| `data` | [bytes](#bytes) | | Data contains bytes to returned from the contract |
@@ -478,7 +519,8 @@ Msg defines the wasm Msg service.
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `StoreCode` | [MsgStoreCode](#cosmwasm.wasm.v1.MsgStoreCode) | [MsgStoreCodeResponse](#cosmwasm.wasm.v1.MsgStoreCodeResponse) | StoreCode to submit Wasm code to the system | |
| `InstantiateContract` | [MsgInstantiateContract](#cosmwasm.wasm.v1.MsgInstantiateContract) | [MsgInstantiateContractResponse](#cosmwasm.wasm.v1.MsgInstantiateContractResponse) | Instantiate creates a new smart contract instance for the given code id. | |
| `InstantiateContract` | [MsgInstantiateContract](#cosmwasm.wasm.v1.MsgInstantiateContract) | [MsgInstantiateContractResponse](#cosmwasm.wasm.v1.MsgInstantiateContractResponse) | InstantiateContract creates a new smart contract instance for the given code id. | |
| `InstantiateContract2` | [MsgInstantiateContract2](#cosmwasm.wasm.v1.MsgInstantiateContract2) | [MsgInstantiateContract2Response](#cosmwasm.wasm.v1.MsgInstantiateContract2Response) | InstantiateContract2 creates a new smart contract instance for the given code id with a predictable address | |
| `ExecuteContract` | [MsgExecuteContract](#cosmwasm.wasm.v1.MsgExecuteContract) | [MsgExecuteContractResponse](#cosmwasm.wasm.v1.MsgExecuteContractResponse) | Execute submits the given message data to a smart contract | |
| `MigrateContract` | [MsgMigrateContract](#cosmwasm.wasm.v1.MsgMigrateContract) | [MsgMigrateContractResponse](#cosmwasm.wasm.v1.MsgMigrateContractResponse) | Migrate runs a code upgrade/ downgrade for a smart contract | |
| `UpdateAdmin` | [MsgUpdateAdmin](#cosmwasm.wasm.v1.MsgUpdateAdmin) | [MsgUpdateAdminResponse](#cosmwasm.wasm.v1.MsgUpdateAdminResponse) | UpdateAdmin sets a new admin for a smart contract | |
@@ -560,7 +602,7 @@ order. The intention is to have more human readable data that is auditable.
| ----- | ---- | ----- | ----------- |
| `store_code` | [MsgStoreCode](#cosmwasm.wasm.v1.MsgStoreCode) | | |
| `instantiate_contract` | [MsgInstantiateContract](#cosmwasm.wasm.v1.MsgInstantiateContract) | | |
| `execute_contract` | [MsgExecuteContract](#cosmwasm.wasm.v1.MsgExecuteContract) | | |
| `execute_contract` | [MsgExecuteContract](#cosmwasm.wasm.v1.MsgExecuteContract) | | MsgInstantiateContract2 intentionally not supported see https://github.com/CosmWasm/wasmd/issues/987 |

View File

@@ -33,6 +33,8 @@ message GenesisState {
MsgStoreCode store_code = 1;
MsgInstantiateContract instantiate_contract = 2;
MsgExecuteContract execute_contract = 3;
// MsgInstantiateContract2 intentionally not supported
// see https://github.com/CosmWasm/wasmd/issues/987
}
}
}

View File

@@ -12,9 +12,14 @@ option (gogoproto.goproto_getters_all) = false;
service Msg {
// StoreCode to submit Wasm code to the system
rpc StoreCode(MsgStoreCode) returns (MsgStoreCodeResponse);
// Instantiate creates a new smart contract instance for the given code id.
// InstantiateContract creates a new smart contract instance for the given
// code id.
rpc InstantiateContract(MsgInstantiateContract)
returns (MsgInstantiateContractResponse);
// InstantiateContract2 creates a new smart contract instance for the given
// code id with a predictable address
rpc InstantiateContract2(MsgInstantiateContract2)
returns (MsgInstantiateContract2Response);
// Execute submits the given message data to a smart contract
rpc ExecuteContract(MsgExecuteContract) returns (MsgExecuteContractResponse);
// Migrate runs a code upgrade/ downgrade for a smart contract
@@ -64,11 +69,45 @@ message MsgInstantiateContract {
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
// MsgInstantiateContract2 create a new smart contract instance for the given
// code id with a predicable address.
message MsgInstantiateContract2 {
// Sender is the that actor that signed the messages
string sender = 1;
// Admin is an optional address that can execute migrations
string admin = 2;
// CodeID is the reference to the stored WASM code
uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ];
// Label is optional metadata to be stored with a contract instance.
string label = 4;
// Msg json encoded message to be passed to the contract on instantiation
bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ];
// Funds coins that are transferred to the contract on instantiation
repeated cosmos.base.v1beta1.Coin funds = 6 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// Salt is an arbitrary value provided by the sender. Size can be 1 to 64.
bytes salt = 7;
// FixMsg include the msg value into the hash for the predictable address.
// Default is false
bool fix_msg = 8;
}
// MsgInstantiateContractResponse return instantiation result data
message MsgInstantiateContractResponse {
// Address is the bech32 address of the new contract instance.
string address = 1;
// Data contains base64-encoded bytes to returned from the contract
// Data contains bytes to returned from the contract
bytes data = 2;
}
// MsgInstantiateContract2Response return instantiation result data
message MsgInstantiateContract2Response {
// Address is the bech32 address of the new contract instance.
string address = 1;
// Data contains bytes to returned from the contract
bytes data = 2;
}
@@ -89,7 +128,7 @@ message MsgExecuteContract {
// MsgExecuteContractResponse returns execution result data.
message MsgExecuteContractResponse {
// Data contains base64-encoded bytes to returned from the contract
// Data contains bytes to returned from the contract
bytes data = 1;
}

View File

@@ -86,6 +86,7 @@ var (
ErrQueryFailed = types.ErrQueryFailed
ErrInvalidMsg = types.ErrInvalidMsg
KeyLastCodeID = types.KeyLastCodeID
KeyLastInstanceID = types.KeyLastInstanceID
CodeKeyPrefix = types.CodeKeyPrefix
ContractKeyPrefix = types.ContractKeyPrefix
ContractStorePrefix = types.ContractStorePrefix
@@ -101,6 +102,7 @@ type (
MsgStoreCode = types.MsgStoreCode
MsgStoreCodeResponse = types.MsgStoreCodeResponse
MsgInstantiateContract = types.MsgInstantiateContract
MsgInstantiateContract2 = types.MsgInstantiateContract2
MsgInstantiateContractResponse = types.MsgInstantiateContractResponse
MsgExecuteContract = types.MsgExecuteContract
MsgExecuteContractResponse = types.MsgExecuteContractResponse

View File

@@ -8,10 +8,6 @@ import (
"errors"
"fmt"
"github.com/CosmWasm/wasmd/x/wasm/ioutils"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
@@ -24,6 +20,8 @@ import (
"github.com/spf13/cobra"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/CosmWasm/wasmd/x/wasm/ioutils"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
@@ -133,7 +131,7 @@ func GenesisInstantiateContractCmd(defaultNodeHome string, genesisMutator Genesi
return fmt.Errorf("permissions were not granted for %s", senderAddr)
}
state.GenMsgs = append(state.GenMsgs, types.GenesisState_GenMsgs{
Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &msg},
Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: msg},
})
return nil
})
@@ -238,10 +236,7 @@ func GenesisListContractsCmd(defaultNodeHome string, genReader GenesisReader) *c
return err
}
state := g.WasmModuleState
all, err := GetAllContracts(state)
if err != nil {
return err
}
all := GetAllContracts(state)
return printJSONOutput(cmd, all)
},
}
@@ -313,18 +308,7 @@ type ContractMeta struct {
Info types.ContractInfo `json:"info"`
}
// returns nil when not found
func codeHashByID(state *types.GenesisState, codeID uint64) []byte {
codes := GetAllCodes(state)
for _, v := range codes {
if v.CodeID == codeID {
return v.Info.CodeHash
}
}
return nil
}
func GetAllContracts(state *types.GenesisState) ([]ContractMeta, error) {
func GetAllContracts(state *types.GenesisState) []ContractMeta {
all := make([]ContractMeta, len(state.Contracts))
for i, c := range state.Contracts {
all[i] = ContractMeta{
@@ -333,18 +317,11 @@ func GetAllContracts(state *types.GenesisState) ([]ContractMeta, error) {
}
}
// add inflight
seq := contractSeqValue(state)
for _, m := range state.GenMsgs {
if msg := m.GetInstantiateContract(); msg != nil {
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
panic(fmt.Sprintf("unsupported address %q: %s", msg.Sender, err))
}
codeHash := codeHashByID(state, msg.CodeID)
if codeHash == nil {
return nil, types.ErrNotFound.Wrapf("hash for code-id: %d", msg.CodeID)
}
all = append(all, ContractMeta{
ContractAddress: keeper.BuildContractAddress(codeHash, senderAddr, msg.Label).String(),
ContractAddress: keeper.BuildContractAddressClassic(msg.CodeID, seq).String(),
Info: types.ContractInfo{
CodeID: msg.CodeID,
Creator: msg.Sender,
@@ -352,9 +329,10 @@ func GetAllContracts(state *types.GenesisState) ([]ContractMeta, error) {
Label: msg.Label,
},
})
seq++
}
}
return all, nil
return all
}
func hasAccountBalance(cmd *cobra.Command, appState map[string]json.RawMessage, sender sdk.AccAddress, coins sdk.Coins) (bool, error) {
@@ -381,19 +359,13 @@ func hasContract(state *types.GenesisState, contractAddr string) bool {
return true
}
}
seq := contractSeqValue(state)
for _, m := range state.GenMsgs {
if msg := m.GetInstantiateContract(); msg != nil {
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
panic(fmt.Sprintf("unsupported address %q: %s", msg.Sender, err))
}
hash := codeHashByID(state, msg.CodeID)
if hash == nil {
panic(fmt.Sprintf("unknown code id: %d", msg.CodeID))
}
if keeper.BuildContractAddress(hash, senderAddr, msg.Label).String() == contractAddr {
if keeper.BuildContractAddressClassic(msg.CodeID, seq).String() == contractAddr {
return true
}
seq++
}
}
return false
@@ -486,6 +458,19 @@ func (x DefaultGenesisIO) AlterWasmModuleState(cmd *cobra.Command, callback func
return genutil.ExportGenesisFile(g.GenDoc, g.GenesisFile)
}
// contractSeqValue reads the contract sequence from the genesis or
// returns default start value used in the keeper
func contractSeqValue(state *types.GenesisState) uint64 {
var seq uint64 = 1
for _, s := range state.Sequences {
if bytes.Equal(s.IDKey, types.KeyLastInstanceID) {
seq = s.Value
break
}
}
return seq
}
// codeSeqValue reads the code sequence from the genesis or
// returns default start value used in the keeper
func codeSeqValue(state *types.GenesisState) uint64 {

View File

@@ -1,15 +1,12 @@
package cli
import (
"bytes"
"context"
"encoding/json"
"os"
"path"
"testing"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
@@ -366,8 +363,7 @@ func TestInstantiateContractCmd(t *testing.T) {
}
func TestExecuteContractCmd(t *testing.T) {
mySenderAddr := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
myFirstContractAddress := keeper.BuildContractAddress([]byte("myCodeHash"), mySenderAddr, "my").String()
const firstContractAddress = "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"
minimalWasmGenesis := types.GenesisState{
Params: types.DefaultParams(),
}
@@ -394,7 +390,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: myFirstContractAddress,
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -403,34 +399,53 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 1,
},
"all good with contract from genesis store messages": {
"all good with contract from genesis store messages without initial sequence": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(func(info *types.CodeInfo) {
info.CodeHash = []byte("myCodeHash")
}),
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture(
func(m *types.MsgInstantiateContract) {
m.Sender = mySenderAddr.String()
m.Label = "my"
})}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 2,
},
"all good with contract from genesis store messages and contract sequence set": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
},
mutator: func(cmd *cobra.Command) {
// See TestBuildContractAddress in keeper_test.go
cmd.SetArgs([]string{"cosmos1mujpjkwhut9yjw4xueyugc02evfv46y0dtmnz4lh8xxkkdapym9stu5qm8", `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
@@ -457,7 +472,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: myFirstContractAddress,
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -466,7 +481,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
},
@@ -484,7 +499,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: myFirstContractAddress,
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -493,7 +508,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("amount", "100stake")
@@ -512,7 +527,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
Contracts: []types.Contract{
{
ContractAddress: myFirstContractAddress,
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
@@ -521,7 +536,7 @@ func TestExecuteContractCmd(t *testing.T) {
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{myFirstContractAddress, `{}`})
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
flagSet.Set("amount", "10stake")
@@ -550,9 +565,6 @@ func TestExecuteContractCmd(t *testing.T) {
}
func TestGetAllContracts(t *testing.T) {
creatorAddr1 := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
creatorAddr2 := sdk.AccAddress(bytes.Repeat([]byte{2}, address.Len))
specs := map[string]struct {
src types.GenesisState
exp []ContractMeta
@@ -583,55 +595,68 @@ func TestGetAllContracts(t *testing.T) {
},
"read from message state": {
src: types.GenesisState{
Codes: []types.Code{{CodeID: 1, CodeInfo: types.CodeInfo{CodeHash: []byte("firstCodeHash")}}},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr1.String(), Label: "first", CodeID: 1}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr2.String(), Label: "second", CodeID: 1}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "first"}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "second"}}},
},
},
exp: []ContractMeta{
{
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
Info: types.ContractInfo{Creator: creatorAddr1.String(), Label: "first", CodeID: 1},
ContractAddress: keeper.BuildContractAddressClassic(0, 1).String(),
Info: types.ContractInfo{Label: "first"},
},
{
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr2, "second").String(),
Info: types.ContractInfo{Creator: creatorAddr2.String(), Label: "second", CodeID: 1},
ContractAddress: keeper.BuildContractAddressClassic(0, 2).String(),
Info: types.ContractInfo{Label: "second"},
},
},
},
"read from message state with contract sequence": {
src: types.GenesisState{
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
},
},
exp: []ContractMeta{
{
ContractAddress: keeper.BuildContractAddressClassic(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
},
},
},
"read from contract and message state with contract sequence": {
src: types.GenesisState{
Codes: []types.Code{
{CodeID: 1, CodeInfo: types.CodeInfo{CodeHash: []byte("firstCodeHash")}},
{CodeID: 100, CodeInfo: types.CodeInfo{CodeHash: []byte("otherCodeHash")}},
},
Contracts: []types.Contract{
{
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
ContractInfo: types.ContractInfo{Label: "first", CodeID: 1},
ContractAddress: "first-contract",
ContractInfo: types.ContractInfo{Label: "first"},
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Sender: creatorAddr1.String(), Label: "hundred", CodeID: 100}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
},
},
exp: []ContractMeta{
{
ContractAddress: keeper.BuildContractAddress([]byte("firstCodeHash"), creatorAddr1, "first").String(),
Info: types.ContractInfo{Label: "first", CodeID: 1},
ContractAddress: "first-contract",
Info: types.ContractInfo{Label: "first"},
},
{
ContractAddress: keeper.BuildContractAddress([]byte("otherCodeHash"), creatorAddr1, "hundred").String(),
Info: types.ContractInfo{Creator: creatorAddr1.String(), Label: "hundred", CodeID: 100},
ContractAddress: keeper.BuildContractAddressClassic(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
},
},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got, err := GetAllContracts(&spec.src)
require.NoError(t, err)
got := GetAllContracts(&spec.src)
assert.Equal(t, spec.exp, got)
})
}

View File

@@ -67,11 +67,12 @@ func GetCmdLibVersion() *cobra.Command {
// GetCmdBuildAddress build a contract address
func GetCmdBuildAddress() *cobra.Command {
decoder := newArgDecoder(hex.DecodeString)
cmd := &cobra.Command{
Use: "build-address [code-hash] [creator-address] [label]",
Use: "build-address [code-hash] [creator-address] [salt-hex-encoded] [json_encoded_init_args (required when set as fixed)]",
Short: "build contract address",
Aliases: []string{"address"},
Args: cobra.ExactArgs(3),
Args: cobra.RangeArgs(3, 4),
RunE: func(cmd *cobra.Command, args []string) error {
codeHash, err := hex.DecodeString(args[0])
if err != nil {
@@ -81,14 +82,27 @@ func GetCmdBuildAddress() *cobra.Command {
if err != nil {
return fmt.Errorf("creator: %s", err)
}
label := args[2]
if err := types.ValidateLabel(label); err != nil {
return fmt.Errorf("label: %s", err)
salt, err := hex.DecodeString(args[2])
switch {
case err != nil:
return fmt.Errorf("salt: %s", err)
case len(salt) == 0:
return errors.New("empty salt")
}
cmd.Println(keeper.BuildContractAddress(codeHash, creator, label).String())
if len(args) == 3 {
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, []byte{}).String())
return nil
}
msg := types.RawContractMessage(args[3])
if err := msg.ValidateBasic(); err != nil {
return fmt.Errorf("init message: %s", err)
}
cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, msg).String())
return nil
},
}
decoder.RegisterFlags(cmd.PersistentFlags(), "salt")
return cmd
}

View File

@@ -1,6 +1,7 @@
package cli
import (
"encoding/hex"
"errors"
"fmt"
"os"
@@ -11,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/version"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
@@ -23,6 +25,7 @@ const (
flagLabel = "label"
flagAdmin = "admin"
flagNoAdmin = "no-admin"
flagFixMsg = "fix-msg"
flagRunAs = "run-as"
flagInstantiateByEverybody = "instantiate-everybody"
flagInstantiateNobody = "instantiate-nobody"
@@ -43,6 +46,7 @@ func GetTxCmd() *cobra.Command {
txCmd.AddCommand(
StoreCodeCmd(),
InstantiateContractCmd(),
InstantiateContract2Cmd(),
ExecuteContractCmd(),
MigrateContractCmd(),
UpdateContractAdminCmd(),
@@ -174,8 +178,14 @@ func parseAccessConfigFlags(flags *flag.FlagSet) (*types.AccessConfig, error) {
// InstantiateContractCmd will instantiate a contract from previously uploaded code.
func InstantiateContractCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "instantiate [code_id_int64] [json_encoded_init_args] --label [text] --admin [address,optional] --amount [coins,optional]",
Use: "instantiate [code_id_int64] [json_encoded_init_args] --label [text] --admin [address,optional] --amount [coins,optional] ",
Short: "Instantiate a wasm contract",
Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message.
Each contract instance has a unique address assigned.
Example:
$ %s wasmd tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey -a)" \
--from mykey --amount="100ustake" --label "local0.1.0"
`, version.AppName, version.AppName),
Aliases: []string{"start", "init", "inst", "i"},
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
@@ -183,7 +193,6 @@ func InstantiateContractCmd() *cobra.Command {
if err != nil {
return err
}
msg, err := parseInstantiateArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags())
if err != nil {
return err
@@ -191,7 +200,7 @@ func InstantiateContractCmd() *cobra.Command {
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
@@ -203,43 +212,105 @@ func InstantiateContractCmd() *cobra.Command {
return cmd
}
func parseInstantiateArgs(rawCodeID, initMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgInstantiateContract, error) {
// InstantiateContract2Cmd will instantiate a contract from previously uploaded code with predicable address generated
func InstantiateContract2Cmd() *cobra.Command {
decoder := newArgDecoder(hex.DecodeString)
cmd := &cobra.Command{
Use: "instantiate2 [code_id_int64] [json_encoded_init_args] [salt] --label [text] --admin [address,optional] --amount [coins,optional] " +
"--fix-msg [bool,optional]",
Short: "Instantiate a wasm contract with predictable address",
Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message.
Each contract instance has a unique address assigned. They are assigned automatically but in order to have predictable addresses
for special use cases, the given 'salt' argument and '--fix-msg' parameters can be used to generate a custom address.
Predictable address example (also see '%s query wasm build-address -h'):
$ %s wasmd tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) --admin="$(%s keys show mykey -a)" \
--from mykey --amount="100ustake" --label "local0.1.0" \
--fix-msg
`, version.AppName, version.AppName, version.AppName),
Aliases: []string{"start", "init", "inst", "i"},
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
salt, err := decoder.DecodeString(args[2])
if err != nil {
return fmt.Errorf("salt: %w", err)
}
fixMsg, err := cmd.Flags().GetBool(flagFixMsg)
if err != nil {
return fmt.Errorf("fix msg: %w", err)
}
data, err := parseInstantiateArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags())
if err != nil {
return err
}
msg := &types.MsgInstantiateContract2{
Sender: data.Sender,
Admin: data.Admin,
CodeID: data.CodeID,
Label: data.Label,
Msg: data.Msg,
Funds: data.Funds,
Salt: salt,
FixMsg: fixMsg,
}
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists")
cmd.Flags().String(flagAdmin, "", "Address of an admin")
cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
cmd.Flags().Bool(flagFixMsg, false, "An optional flag to include the json_encoded_init_args for the predictable address generation mode")
decoder.RegisterFlags(cmd.PersistentFlags(), "salt")
flags.AddTxFlagsToCmd(cmd)
return cmd
}
func parseInstantiateArgs(rawCodeID, initMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (*types.MsgInstantiateContract, error) {
// get the id of the code to instantiate
codeID, err := strconv.ParseUint(rawCodeID, 10, 64)
if err != nil {
return types.MsgInstantiateContract{}, err
return nil, err
}
amountStr, err := flags.GetString(flagAmount)
if err != nil {
return types.MsgInstantiateContract{}, fmt.Errorf("amount: %s", err)
return nil, fmt.Errorf("amount: %s", err)
}
amount, err := sdk.ParseCoinsNormalized(amountStr)
if err != nil {
return types.MsgInstantiateContract{}, fmt.Errorf("amount: %s", err)
return nil, fmt.Errorf("amount: %s", err)
}
label, err := flags.GetString(flagLabel)
if err != nil {
return types.MsgInstantiateContract{}, fmt.Errorf("label: %s", err)
return nil, fmt.Errorf("label: %s", err)
}
if label == "" {
return types.MsgInstantiateContract{}, errors.New("label is required on all contracts")
return nil, errors.New("label is required on all contracts")
}
adminStr, err := flags.GetString(flagAdmin)
if err != nil {
return types.MsgInstantiateContract{}, fmt.Errorf("admin: %s", err)
return nil, fmt.Errorf("admin: %s", err)
}
noAdmin, err := flags.GetBool(flagNoAdmin)
if err != nil {
return types.MsgInstantiateContract{}, fmt.Errorf("no-admin: %s", err)
return nil, fmt.Errorf("no-admin: %s", err)
}
// ensure sensible admin is set (or explicitly immutable)
if adminStr == "" && !noAdmin {
return types.MsgInstantiateContract{}, fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)")
return nil, fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)")
}
if adminStr != "" && noAdmin {
return types.MsgInstantiateContract{}, fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true")
return nil, fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true")
}
// build and sign the transaction, then broadcast to Tendermint
@@ -251,7 +322,7 @@ func parseInstantiateArgs(rawCodeID, initMsg string, sender sdk.AccAddress, flag
Msg: []byte(initMsg),
Admin: adminStr,
}
return msg, nil
return &msg, nil
}
// ExecuteContractCmd will instantiate a contract from previously uploaded code.

View File

@@ -29,6 +29,8 @@ func NewHandler(k types.ContractOpsKeeper) sdk.Handler {
res, err = msgServer.StoreCode(sdk.WrapSDKContext(ctx), msg)
case *MsgInstantiateContract:
res, err = msgServer.InstantiateContract(sdk.WrapSDKContext(ctx), msg)
case *MsgInstantiateContract2:
res, err = msgServer.InstantiateContract2(sdk.WrapSDKContext(ctx), msg)
case *MsgExecuteContract:
res, err = msgServer.ExecuteContract(sdk.WrapSDKContext(ctx), msg)
case *MsgMigrateContract:

View File

@@ -0,0 +1,76 @@
package keeper
import (
"encoding/binary"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
// AddressGenerator abstract address generator to be used for a single contract address
type AddressGenerator func(ctx sdk.Context, codeID uint64, checksum []byte) sdk.AccAddress
// ClassicAddressGenerator generates a contract address using codeID and instanceID sequence
func (k Keeper) ClassicAddressGenerator() AddressGenerator {
return func(ctx sdk.Context, codeID uint64, _ []byte) sdk.AccAddress {
instanceID := k.autoIncrementID(ctx, types.KeyLastInstanceID)
return BuildContractAddressClassic(codeID, instanceID)
}
}
// PredicableAddressGenerator generates a predictable contract address
func PredicableAddressGenerator(creator sdk.AccAddress, salt []byte, msg []byte, fixMsg bool) AddressGenerator {
return func(ctx sdk.Context, _ uint64, checksum []byte) sdk.AccAddress {
if !fixMsg { // clear msg to not be included in the address generation
msg = []byte{}
}
return BuildContractAddressPredictable(checksum, creator, salt, msg)
}
}
// BuildContractAddressClassic builds an sdk account address for a contract.
func BuildContractAddressClassic(codeID, instanceID uint64) sdk.AccAddress {
contractID := make([]byte, 16)
binary.BigEndian.PutUint64(contractID[:8], codeID)
binary.BigEndian.PutUint64(contractID[8:], instanceID)
return address.Module(types.ModuleName, contractID)[:types.ContractAddrLen]
}
// BuildContractAddressPredictable generates a contract address for the wasm module with len = types.ContractAddrLen using the
// Cosmos SDK address.Module function.
// Internally a key is built containing:
// (len(checksum) | checksum | len(sender_address) | sender_address | len(salt) | salt| len(initMsg) | initMsg).
//
// All method parameter values must be valid and not nil.
func BuildContractAddressPredictable(checksum []byte, creator sdk.AccAddress, salt, initMsg types.RawContractMessage) sdk.AccAddress {
if len(checksum) != 32 {
panic("invalid checksum")
}
if err := sdk.VerifyAddressFormat(creator); err != nil {
panic(fmt.Sprintf("creator: %s", err))
}
if err := types.ValidateSalt(salt); err != nil {
panic(fmt.Sprintf("salt: %s", err))
}
if err := initMsg.ValidateBasic(); len(initMsg) != 0 && err != nil {
panic(fmt.Sprintf("initMsg: %s", err))
}
checksum = UInt64LengthPrefix(checksum)
creator = UInt64LengthPrefix(creator)
salt = UInt64LengthPrefix(salt)
initMsg = UInt64LengthPrefix(initMsg)
key := make([]byte, len(checksum)+len(creator)+len(salt)+len(initMsg))
copy(key[0:], checksum)
copy(key[len(checksum):], creator)
copy(key[len(checksum)+len(creator):], salt)
copy(key[len(checksum)+len(creator)+len(salt):], initMsg)
return address.Module(types.ModuleName, key)[:types.ContractAddrLen]
}
// UInt64LengthPrefix prepend big endian encoded byte length
func UInt64LengthPrefix(bz []byte) []byte {
return append(sdk.Uint64ToBigEndian(uint64(len(bz))), bz...)
}

View File

@@ -0,0 +1,432 @@
package keeper
import (
"encoding/json"
"fmt"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
)
func TestBuildContractAddress(t *testing.T) {
x, y := sdk.GetConfig().GetBech32AccountAddrPrefix(), sdk.GetConfig().GetBech32AccountPubPrefix()
t.Cleanup(func() {
sdk.GetConfig().SetBech32PrefixForAccount(x, y)
})
sdk.GetConfig().SetBech32PrefixForAccount("purple", "purple")
// test vectors generated via cosmjs: https://github.com/cosmos/cosmjs/pull/1253/files
type Spec struct {
In struct {
Checksum tmbytes.HexBytes `json:"checksum"`
Creator sdk.AccAddress `json:"creator"`
Salt tmbytes.HexBytes `json:"salt"`
Msg string `json:"msg"`
} `json:"in"`
Out struct {
Address sdk.AccAddress `json:"address"`
} `json:"out"`
}
var specs []Spec
require.NoError(t, json.Unmarshal([]byte(goldenMasterPredictableContractAddr), &specs))
require.NotEmpty(t, specs)
for i, spec := range specs {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
// when
gotAddr := BuildContractAddressPredictable(spec.In.Checksum, spec.In.Creator, spec.In.Salt.Bytes(), []byte(spec.In.Msg))
require.Equal(t, spec.Out.Address.String(), gotAddr.String())
require.NoError(t, sdk.VerifyAddressFormat(gotAddr))
})
}
}
const goldenMasterPredictableContractAddr = `[
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": null
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000001610000000000000000",
"addressData": "5e865d3e45ad3e961f77fd77d46543417ced44d924dc3e079b5415ff6775f847"
},
"out": {
"address": "purple1t6r960j945lfv8mhl4mage2rg97w63xeynwrupum2s2l7em4lprs9ce5hk"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": "{}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc00000000000000016100000000000000027b7d",
"addressData": "0995499608947a5281e2c7ebd71bdb26a1ad981946dad57f6c4d3ee35de77835"
},
"out": {
"address": "purple1px25n9sgj3a99q0zcl4awx7my6s6mxqegmdd2lmvf5lwxh080q6suttktr"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "83326e554723b15bac664ceabc8a5887e27003abe9fbd992af8c7bcea4745167"
},
"out": {
"address": "purple1svexu428ywc4htrxfn4tezjcsl38qqata8aany4033auafr529ns4v254c"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": null
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000",
"addressData": "9384c6248c0bb171e306fd7da0993ec1e20eba006452a3a9e078883eb3594564"
},
"out": {
"address": "purple1jwzvvfyvpwchrccxl476pxf7c83qawsqv3f2820q0zyrav6eg4jqdcq7gc"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d",
"addressData": "9a8d5f98fb186825401a26206158e7a1213311a9b6a87944469913655af52ffb"
},
"out": {
"address": "purple1n2x4lx8mrp5z2sq6ycsxzk885ysnxydfk658j3zxnyfk2kh49lasesxf6j"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "932f07bc53f7d0b0b43cb5a54ac3e245b205e6ae6f7c1d991dc6af4a2ff9ac18"
},
"out": {
"address": "purple1jvhs00zn7lgtpdpukkj54slzgkeqte4wda7pmxgac6h55tle4svq8cmp60"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": null
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000001610000000000000000",
"addressData": "9725e94f528d8b78d33c25f3dfcd60e6142d8be60ab36f6a5b59036fd51560db"
},
"out": {
"address": "purple1juj7jn6j3k9h35euyhealntquc2zmzlxp2ek76jmtypkl4g4vrdsfwmwxk"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": "{}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff00000000000000016100000000000000027b7d",
"addressData": "b056e539bbaf447ba18f3f13b792970111fc78933eb6700f4d227b5216d63658"
},
"out": {
"address": "purple1kptw2wdm4az8hgv08ufm0y5hqyglc7yn86m8qr6dyfa4y9kkxevqmkm9q3"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "6c98434180f052294ff89fb6d2dae34f9f4468b0b8e6e7c002b2a44adee39abd"
},
"out": {
"address": "purple1djvyxsvq7pfzjnlcn7md9khrf705g69shrnw0sqzk2jy4hhrn27sjh2ysy"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": null
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000",
"addressData": "0aaf1c31c5d529d21d898775bc35b3416f47bfd99188c334c6c716102cbd3101"
},
"out": {
"address": "purple1p2h3cvw9655ay8vfsa6mcddng9h5007ejxyvxdxxcutpqt9axyqsagmmay"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d",
"addressData": "36fe6ab732187cdfed46290b448b32eb7f4798e7a4968b0537de8a842cbf030e"
},
"out": {
"address": "purple1xmlx4dejrp7dlm2x9y95fzejadl50x885jtgkpfhm69ggt9lqv8qk3vn4f"
}
},
{
"in": {
"checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "a0d0c942adac6f3e5e7c23116c4c42a24e96e0ab75f53690ec2d3de16067c751"
},
"out": {
"address": "purple15rgvjs4d43hnuhnuyvgkcnzz5f8fdc9twh6ndy8v9577zcr8cags40l9dt"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": null
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000001610000000000000000",
"addressData": "b95c467218d408a0f93046f227b6ece7fe18133ff30113db4d2a7becdfeca141"
},
"out": {
"address": "purple1h9wyvusc6sy2p7fsgmez0dhvullpsyel7vq38k6d9fa7ehlv59qsvnyh36"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": "{}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc00000000000000016100000000000000027b7d",
"addressData": "23fe45dbbd45dc6cd25244a74b6e99e7a65bf0bac2f2842a05049d37555a3ae6"
},
"out": {
"address": "purple1y0lytkaaghwxe5jjgjn5km5eu7n9hu96ctegg2s9qjwnw4268tnqxhg60a"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "61",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "6faea261ed63baa65b05726269e83b217fa6205dc7d9fb74f9667d004a69c082"
},
"out": {
"address": "purple1d7h2yc0dvwa2vkc9wf3xn6pmy9l6vgzaclvlka8eve7sqjnfczpqqsdnwu"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": null
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000",
"addressData": "67a3ab6384729925fdb144574628ce96836fe098d2c6be4e84ac106b2728d96c"
},
"out": {
"address": "purple1v736kcuyw2vjtld3g3t5v2xwj6pklcyc6trtun5y4sgxkfegm9kq7vgpnt"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d",
"addressData": "23a121263bfce05c144f4af86f3d8a9f87dc56f9dc48dbcffc8c5a614da4c661"
},
"out": {
"address": "purple1ywsjzf3mlns9c9z0ftux70v2n7rac4hem3ydhnlu33dxzndycesssc7x2m"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "dd90dba6d6fcd5fb9c9c8f536314eb1bb29cb5aa084b633c5806b926a5636b58"
},
"out": {
"address": "purple1mkgdhfkkln2lh8yu3afkx98trwefedd2pp9kx0zcq6ujdftrddvq50esay"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": null
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000001610000000000000000",
"addressData": "547a743022f4f1af05b102f57bf1c1c7d7ee81bae427dc20d36b2c4ec57612ae"
},
"out": {
"address": "purple123a8gvpz7nc67pd3qt6hhuwpclt7aqd6usnacgxndvkya3tkz2hq5hz38f"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": "{}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff00000000000000016100000000000000027b7d",
"addressData": "416e169110e4b411bc53162d7503b2bbf14d6b36b1413a4f4c9af622696e9665"
},
"out": {
"address": "purple1g9hpdygsuj6pr0znzckh2qajh0c566ekk9qn5n6vntmzy6twjejsrl9alk"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "61",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "619a0988b92d8796cea91dea63cbb1f1aefa4a6b6ee5c5d1e937007252697220"
},
"out": {
"address": "purple1vxdqnz9e9kredn4frh4x8ja37xh05jntdmjut50fxuq8y5nfwgsquu9mxh"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": null
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000",
"addressData": "d8af856a6a04852d19b647ad6d4336eb26e077f740aef1a0331db34d299a885a"
},
"out": {
"address": "purple1mzhc26n2qjzj6xdkg7kk6sekavnwqalhgzh0rgpnrke562v63pdq8grp8q"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d",
"addressData": "c7fb7bea96daab23e416c4fcf328215303005e1d0d5424257335568e5381e33c"
},
"out": {
"address": "purple1clahh65km24j8eqkcn70x2pp2vpsqhsap42zgftnx4tgu5upuv7q9ywjws"
}
},
{
"in": {
"checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b",
"creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
"creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff",
"salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
"msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}"
},
"intermediate": {
"key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d",
"addressData": "ccdf9dea141a6c2475870529ab38fae9dec30df28e005894fe6578b66133ab4a"
},
"out": {
"address": "purple1en0em6s5rfkzgav8q556kw86a80vxr0j3cq93987v4utvcfn4d9q0tql4w"
}
}
]
`

View File

@@ -11,7 +11,18 @@ var _ types.ContractOpsKeeper = PermissionedKeeper{}
// decoratedKeeper contains a subset of the wasm keeper that are already or can be guarded by an authorization policy in the future
type decoratedKeeper interface {
create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error)
instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.AccAddress, []byte, error)
instantiate(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
addressGenerator AddressGenerator,
authZ AuthorizationPolicy,
) (sdk.AccAddress, []byte, error)
migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error)
setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error
pinCode(ctx sdk.Context, codeID uint64) error
@@ -20,6 +31,7 @@ type decoratedKeeper interface {
Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error)
setContractInfoExtension(ctx sdk.Context, contract sdk.AccAddress, extra types.ContractInfoExtension) error
setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, autz AuthorizationPolicy) error
ClassicAddressGenerator() AddressGenerator
}
type PermissionedKeeper struct {
@@ -43,8 +55,40 @@ func (p PermissionedKeeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasm
return p.nested.create(ctx, creator, wasmCode, instantiateAccess, p.authZPolicy)
}
func (p PermissionedKeeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) {
return p.nested.instantiate(ctx, codeID, creator, admin, initMsg, label, deposit, p.authZPolicy)
// Instantiate creates an instance of a WASM contract using the classic sequence based address generator
func (p PermissionedKeeper) Instantiate(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
) (sdk.AccAddress, []byte, error) {
return p.nested.instantiate(ctx, codeID, creator, admin, initMsg, label, deposit, p.nested.ClassicAddressGenerator(), p.authZPolicy)
}
// Instantiate2 creates an instance of a WASM contract using the predictable address generator
func (p PermissionedKeeper) Instantiate2(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
salt []byte,
fixMsg bool,
) (sdk.AccAddress, []byte, error) {
return p.nested.instantiate(
ctx,
codeID,
creator,
admin,
initMsg,
label,
deposit,
PredicableAddressGenerator(creator, salt, initMsg, fixMsg),
p.authZPolicy,
)
}
func (p PermissionedKeeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) {

View File

@@ -0,0 +1,168 @@
package keeper
import (
"encoding/json"
"fmt"
"math"
"strings"
"testing"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
func TestInstantiate2(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example := StoreHackatomExampleContract(t, parentCtx, keepers)
otherExample := StoreReflectContract(t, parentCtx, keepers)
mock := &wasmtesting.MockWasmer{}
wasmtesting.MakeInstantiable(mock)
keepers.WasmKeeper.wasmVM = mock // set mock to not fail on contract init message
verifierAddr := RandomAccountAddress(t)
beneficiaryAddr := RandomAccountAddress(t)
initMsg := mustMarshal(t, HackatomExampleInitMsg{Verifier: verifierAddr, Beneficiary: beneficiaryAddr})
otherAddr := keepers.Faucet.NewFundedRandomAccount(parentCtx, sdk.NewInt64Coin("denom", 1_000_000_000))
const (
mySalt = "my salt"
myLabel = "my label"
)
// create instances for duplicate checks
exampleContract := func(t *testing.T, ctx sdk.Context, fixMsg bool) {
_, _, err := keepers.ContractKeeper.Instantiate2(
ctx,
example.CodeID,
example.CreatorAddr,
nil,
initMsg,
myLabel,
sdk.NewCoins(sdk.NewInt64Coin("denom", 1)),
[]byte(mySalt),
fixMsg,
)
require.NoError(t, err)
}
exampleWithFixMsg := func(t *testing.T, ctx sdk.Context) {
exampleContract(t, ctx, true)
}
exampleWithoutFixMsg := func(t *testing.T, ctx sdk.Context) {
exampleContract(t, ctx, false)
}
specs := map[string]struct {
setup func(t *testing.T, ctx sdk.Context)
codeID uint64
sender sdk.AccAddress
salt []byte
initMsg json.RawMessage
fixMsg bool
expErr error
}{
"fix msg - generates different address than without fixMsg": {
setup: exampleWithoutFixMsg,
codeID: example.CodeID,
sender: example.CreatorAddr,
salt: []byte(mySalt),
initMsg: initMsg,
fixMsg: true,
},
"fix msg - different sender": {
setup: exampleWithFixMsg,
codeID: example.CodeID,
sender: otherAddr,
salt: []byte(mySalt),
initMsg: initMsg,
fixMsg: true,
},
"fix msg - different code": {
setup: exampleWithFixMsg,
codeID: otherExample.CodeID,
sender: example.CreatorAddr,
salt: []byte(mySalt),
initMsg: []byte(`{}`),
fixMsg: true,
},
"fix msg - different salt": {
setup: exampleWithFixMsg,
codeID: example.CodeID,
sender: example.CreatorAddr,
salt: []byte("other salt"),
initMsg: initMsg,
fixMsg: true,
},
"fix msg - different init msg": {
setup: exampleWithFixMsg,
codeID: example.CodeID,
sender: example.CreatorAddr,
salt: []byte(mySalt),
initMsg: mustMarshal(t, HackatomExampleInitMsg{Verifier: otherAddr, Beneficiary: beneficiaryAddr}),
fixMsg: true,
},
"different sender": {
setup: exampleWithoutFixMsg,
codeID: example.CodeID,
sender: otherAddr,
salt: []byte(mySalt),
initMsg: initMsg,
},
"different code": {
setup: exampleWithoutFixMsg,
codeID: otherExample.CodeID,
sender: example.CreatorAddr,
salt: []byte(mySalt),
initMsg: []byte(`{}`),
},
"different salt": {
setup: exampleWithoutFixMsg,
codeID: example.CodeID,
sender: example.CreatorAddr,
salt: []byte(`other salt`),
initMsg: initMsg,
},
"different init msg - reject same address": {
setup: exampleWithoutFixMsg,
codeID: example.CodeID,
sender: example.CreatorAddr,
salt: []byte(mySalt),
initMsg: mustMarshal(t, HackatomExampleInitMsg{Verifier: otherAddr, Beneficiary: beneficiaryAddr}),
expErr: types.ErrDuplicate,
},
"fix msg - long msg": {
setup: exampleWithFixMsg,
codeID: example.CodeID,
sender: otherAddr,
salt: []byte(mySalt),
initMsg: []byte(fmt.Sprintf(`{"foo":%q}`, strings.Repeat("b", math.MaxInt16+1))), // too long kills CI
fixMsg: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
spec.setup(t, ctx)
gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate2(
ctx,
spec.codeID,
spec.sender,
nil,
spec.initMsg,
myLabel,
sdk.NewCoins(sdk.NewInt64Coin("denom", 2)),
spec.salt,
spec.fixMsg,
)
if spec.expErr != nil {
assert.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.NotEmpty(t, gotAddr)
})
}
}

View File

@@ -35,6 +35,7 @@ func InitGenesis(ctx sdk.Context, keeper *Keeper, data types.GenesisState, staki
}
}
var maxContractID int
for i, contract := range data.Contracts {
contractAddr, err := sdk.AccAddressFromBech32(contract.ContractAddress)
if err != nil {
@@ -44,6 +45,7 @@ func InitGenesis(ctx sdk.Context, keeper *Keeper, data types.GenesisState, staki
if err != nil {
return nil, sdkerrors.Wrapf(err, "contract number %d", i)
}
maxContractID = i + 1 // not ideal but max(contractID) is not persisted otherwise
}
for i, seq := range data.Sequences {
@@ -58,6 +60,10 @@ func InitGenesis(ctx sdk.Context, keeper *Keeper, data types.GenesisState, staki
if seqVal <= maxCodeID {
return nil, sdkerrors.Wrapf(types.ErrInvalid, "seq %s with value: %d must be greater than: %d ", string(types.KeyLastCodeID), seqVal, maxCodeID)
}
seqVal = keeper.PeekAutoIncrementID(ctx, types.KeyLastInstanceID)
if seqVal <= uint64(maxContractID) {
return nil, sdkerrors.Wrapf(types.ErrInvalid, "seq %s with value: %d must be greater than: %d ", string(types.KeyLastInstanceID), seqVal, maxContractID)
}
if len(data.GenMsgs) == 0 {
return nil, nil
@@ -111,7 +117,7 @@ func ExportGenesis(ctx sdk.Context, keeper *Keeper) *types.GenesisState {
return false
})
for _, k := range [][]byte{types.KeyLastCodeID} {
for _, k := range [][]byte{types.KeyLastCodeID, types.KeyLastInstanceID} {
genState.Sequences = append(genState.Sequences, types.Sequence{
IDKey: k,
Value: keeper.PeekAutoIncrementID(ctx, k),

View File

@@ -11,14 +11,11 @@ import (
"testing"
"time"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
@@ -69,7 +66,7 @@ func TestGenesisExportImport(t *testing.T) {
creatorAddr, err := sdk.AccAddressFromBech32(codeInfo.Creator)
require.NoError(t, err)
codeID, checksum, err := contractKeeper.Create(srcCtx, creatorAddr, wasmCode, &codeInfo.InstantiateConfig)
codeID, _, err := contractKeeper.Create(srcCtx, creatorAddr, wasmCode, &codeInfo.InstantiateConfig)
require.NoError(t, err)
if pinned {
contractKeeper.PinCode(srcCtx, codeID)
@@ -84,7 +81,7 @@ func TestGenesisExportImport(t *testing.T) {
}
contract.CodeID = codeID
contractAddr := BuildContractAddress(checksum, creatorAddr, "testing")
contractAddr := wasmKeeper.ClassicAddressGenerator()(srcCtx, codeID, nil)
wasmKeeper.storeContractInfo(srcCtx, contractAddr, &contract)
wasmKeeper.appendToContractHistory(srcCtx, contractAddr, history...)
wasmKeeper.importContractState(srcCtx, contractAddr, stateModels)
@@ -160,16 +157,6 @@ func TestGenesisInit(t *testing.T) {
require.NoError(t, err)
myCodeInfo := wasmTypes.CodeInfoFixture(wasmTypes.WithSHA256CodeHash(wasmCode))
mySenderAddr := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
myLabel := "testing"
myContractInfoFixture := func(mutators ...func(*types.ContractInfo)) types.ContractInfo {
return types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) {
c.CodeID = 1
c.Creator = mySenderAddr.String()
c.Label = myLabel
}, types.OnlyGenesisFields)
}
specs := map[string]struct {
src types.GenesisState
stakingMock StakingKeeperMock
@@ -185,6 +172,7 @@ func TestGenesisInit(t *testing.T) {
}},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
Params: types.DefaultParams(),
},
@@ -203,6 +191,7 @@ func TestGenesisInit(t *testing.T) {
}},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 10},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
Params: types.DefaultParams(),
},
@@ -222,6 +211,7 @@ func TestGenesisInit(t *testing.T) {
Contracts: nil,
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 3},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
Params: types.DefaultParams(),
},
@@ -278,12 +268,13 @@ func TestGenesisInit(t *testing.T) {
}},
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 2},
},
Params: types.DefaultParams(),
},
@@ -298,17 +289,16 @@ func TestGenesisInit(t *testing.T) {
}},
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
}, {
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, "other-label").String(),
ContractInfo: myContractInfoFixture(func(i *wasmTypes.ContractInfo) {
i.Label = "other-label"
}),
ContractAddress: BuildContractAddressClassic(1, 2).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 3},
},
Params: types.DefaultParams(),
},
@@ -318,8 +308,8 @@ func TestGenesisInit(t *testing.T) {
src: types.GenesisState{
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
},
},
Params: types.DefaultParams(),
@@ -334,11 +324,11 @@ func TestGenesisInit(t *testing.T) {
}},
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
}, {
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
},
},
Params: types.DefaultParams(),
@@ -353,8 +343,8 @@ func TestGenesisInit(t *testing.T) {
}},
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddress(myCodeInfo.CodeHash, mySenderAddr, myLabel).String(),
ContractInfo: myContractInfoFixture(),
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
ContractState: []types.Model{
{
Key: []byte{0x1},
@@ -392,6 +382,26 @@ func TestGenesisInit(t *testing.T) {
Params: types.DefaultParams(),
},
},
"prevent contract id seq init value == count contracts": {
src: types.GenesisState{
Codes: []types.Code{{
CodeID: firstCodeID,
CodeInfo: myCodeInfo,
CodeBytes: wasmCode,
}},
Contracts: []types.Contract{
{
ContractAddress: BuildContractAddressClassic(1, 1).String(),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }, types.OnlyGenesisFields),
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
Params: types.DefaultParams(),
},
},
"validator set update called for any genesis messages": {
src: wasmTypes.GenesisState{
GenMsgs: []types.GenesisState_GenMsgs{
@@ -550,17 +560,16 @@ func TestImportContractWithCodeHistoryReset(t *testing.T) {
}
assert.Equal(t, expHistory, keeper.GetContractHistory(ctx, contractAddr))
assert.Equal(t, uint64(2), keeper.PeekAutoIncrementID(ctx, types.KeyLastCodeID))
assert.Equal(t, uint64(3), keeper.PeekAutoIncrementID(ctx, types.KeyLastInstanceID))
}
func TestSupportedGenMsgTypes(t *testing.T) {
wasmCode, err := os.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
wasmHash := sha256.Sum256(wasmCode)
var (
myAddress sdk.AccAddress = bytes.Repeat([]byte{1}, types.ContractAddrLen)
verifierAddress sdk.AccAddress = bytes.Repeat([]byte{2}, types.ContractAddrLen)
beneficiaryAddress sdk.AccAddress = bytes.Repeat([]byte{3}, types.ContractAddrLen)
contractAddr = BuildContractAddress(wasmHash[:], myAddress, "testing")
)
const denom = "stake"
importState := types.GenesisState{
@@ -592,7 +601,7 @@ func TestSupportedGenMsgTypes(t *testing.T) {
Sum: &types.GenesisState_GenMsgs_ExecuteContract{
ExecuteContract: &types.MsgExecuteContract{
Sender: verifierAddress.String(),
Contract: contractAddr.String(),
Contract: BuildContractAddressClassic(1, 1).String(),
Msg: []byte(`{"release":{}}`),
},
},
@@ -617,7 +626,7 @@ func TestSupportedGenMsgTypes(t *testing.T) {
require.NotNil(t, codeInfo)
// verify contract instantiated
cInfo := keeper.GetContractInfo(ctx, contractAddr)
cInfo := keeper.GetContractInfo(ctx, BuildContractAddressClassic(1, 1))
require.NotNil(t, cInfo)
// verify contract executed

View File

@@ -4,8 +4,6 @@ import (
"fmt"
"testing"
"github.com/tendermint/tendermint/libs/rand"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
@@ -43,7 +41,7 @@ func TestBindingPortForIBCContractOnInstantiate(t *testing.T) {
}
func TestContractFromPortID(t *testing.T) {
contractAddr := BuildContractAddress(rand.Bytes(32), RandomAccountAddress(t), "testing")
contractAddr := BuildContractAddressClassic(1, 100)
specs := map[string]struct {
srcPort string
expAddr sdk.AccAddress

View File

@@ -19,7 +19,6 @@ import (
"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
@@ -224,8 +223,8 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte,
evt := sdk.NewEvent(
types.EventTypeStoreCode,
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
sdk.NewAttribute(types.AttributeKeyChecksum, hex.EncodeToString(checksum)),
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), // last element to be compatible with scripts
)
for _, f := range strings.Split(report.RequiredCapabilities, ",") {
evt.AppendAttributes(sdk.NewAttribute(types.AttributeKeyRequiredCapability, strings.TrimSpace(f)))
@@ -267,7 +266,16 @@ func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeIn
return nil
}
func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authPolicy AuthorizationPolicy) (sdk.AccAddress, []byte, error) {
func (k Keeper) instantiate(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
addressGenerator AddressGenerator,
authPolicy AuthorizationPolicy,
) (sdk.AccAddress, []byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate")
if creator == nil {
@@ -277,19 +285,15 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
ctx.GasMeter().ConsumeGas(instanceCosts, "Loading CosmWasm module: instantiate")
// get contact info
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetCodeKey(codeID))
if bz == nil {
codeInfo := k.GetCodeInfo(ctx, codeID)
if codeInfo == nil {
return nil, nil, sdkerrors.Wrap(types.ErrNotFound, "code")
}
var codeInfo types.CodeInfo
k.cdc.MustUnmarshal(bz, &codeInfo)
if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) {
return nil, nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate")
}
contractAddress := BuildContractAddress(codeInfo.CodeHash, creator, label)
contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash)
if k.HasContractInfo(ctx, contractAddress) {
return nil, nil, types.ErrDuplicate.Wrap("instance with this code id, sender and label exists: try a different label")
}
@@ -338,7 +342,7 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
info := types.NewInfo(creator, deposit)
// create prefixed data store
// 0x03 | BuildContractAddress (sdk.AccAddress)
// 0x03 | BuildContractAddressClassic (sdk.AccAddress)
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
@@ -1006,21 +1010,6 @@ func (k Keeper) consumeRuntimeGas(ctx sdk.Context, gas uint64) {
}
}
// BuildContractAddress generates a contract address for the wasm module with len = types.ContractAddrLen using the
// Cosmos SDK address.Module function.
// Internally a key is built containing (len(checksum) | checksum | len(sender_address) | sender_address | len(label) | label).
// All method parameter values must be valid and not be empty or nil.
func BuildContractAddress(checksum []byte, creator sdk.AccAddress, label string) sdk.AccAddress {
checksum = address.MustLengthPrefix(checksum)
creator = address.MustLengthPrefix(creator)
labelBz := address.MustLengthPrefix([]byte(label))
key := make([]byte, len(checksum)+len(creator)+len(labelBz))
copy(key[0:], checksum)
copy(key[len(checksum):], creator)
copy(key[len(checksum)+len(creator):], labelBz)
return address.Module(types.ModuleName, key)[:types.ContractAddrLen]
}
func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 {
store := ctx.KVStore(k.storeKey)
bz := store.Get(lastIDKey)

View File

@@ -3,12 +3,10 @@ package keeper
import (
"bytes"
_ "embed"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"testing"
"time"
@@ -62,7 +60,7 @@ func TestCreateSuccess(t *testing.T) {
require.Equal(t, hackatomWasm, storedCode)
// and events emitted
codeHash := "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5"
exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_id", "1"), sdk.NewAttribute("code_checksum", codeHash))}
exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_checksum", codeHash), sdk.NewAttribute("code_id", "1"))}
assert.Equal(t, exp, em.Events())
}
@@ -398,11 +396,11 @@ func TestInstantiate(t *testing.T) {
// create with no balance is also legal
gotContractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos1xaq0tcwz9fsqmtxlpzwjn2zr8gw66ljjr079ltfc5pelepcs7sjsk28n5n", gotContractAddr.String())
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", gotContractAddr.String())
gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x187b8), gasAfter-gasBefore)
require.Equal(t, uint64(0x1964f), gasAfter-gasBefore)
}
// ensure it is stored properly
@@ -541,65 +539,6 @@ func TestInstantiateWithPermissions(t *testing.T) {
}
}
func TestInstantiateWithUniqueContractAddress(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example := InstantiateHackatomExampleContract(t, parentCtx, keepers)
otherExample := InstantiateReflectExampleContract(t, parentCtx, keepers)
initMsg := mustMarshal(t, HackatomExampleInitMsg{Verifier: example.VerifierAddr, Beneficiary: example.BeneficiaryAddr})
otherAddress := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, otherAddress, sdk.NewInt64Coin("denom", 100000000))
used := make(map[string]struct{})
specs := map[string]struct {
codeID uint64
sender sdk.AccAddress
label string
initMsg json.RawMessage
expErr error
}{
"reject duplicate which generates the same address": {
codeID: example.CodeID,
sender: example.CreatorAddr,
label: example.Label,
initMsg: initMsg,
expErr: types.ErrDuplicate,
},
"different sender": {
codeID: example.CodeID,
sender: otherAddress,
label: example.Label,
initMsg: initMsg,
},
"different code": {
codeID: otherExample.CodeID,
sender: example.CreatorAddr,
label: example.Label,
initMsg: []byte(`{}`),
},
"different label": {
codeID: example.CodeID,
sender: example.CreatorAddr,
label: "other label",
initMsg: initMsg,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate(ctx, spec.codeID, spec.sender, nil, spec.initMsg, spec.label, example.Deposit)
if spec.expErr != nil {
assert.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
expAddr := BuildContractAddress(keepers.WasmKeeper.GetCodeInfo(ctx, spec.codeID).CodeHash, spec.sender, spec.label)
assert.Equal(t, expAddr.String(), gotAddr.String())
require.NotContains(t, used, gotAddr.String())
used[gotAddr.String()] = struct{}{}
})
}
}
func TestInstantiateWithAccounts(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example := StoreHackatomExampleContract(t, parentCtx, keepers)
@@ -609,12 +548,13 @@ func TestInstantiateWithAccounts(t *testing.T) {
senderAddr := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, senderAddr, sdk.NewInt64Coin("denom", 100000000))
const myLabel = "testing"
contractAddr := BuildContractAddress(example.Checksum, senderAddr, myLabel)
mySalt := []byte(`my salt`)
contractAddr := BuildContractAddressPredictable(example.Checksum, senderAddr, mySalt, []byte{})
lastAccountNumber := keepers.AccountKeeper.GetAccount(parentCtx, senderAddr).GetAccountNumber()
specs := map[string]struct {
acceptList Option
option Option
account authtypes.AccountI
initBalance sdk.Coin
deposit sdk.Coins
@@ -679,7 +619,7 @@ func TestInstantiateWithAccounts(t *testing.T) {
expErr: types.ErrAccountExists,
},
"with option used to set non default type to accept list": {
acceptList: WithAcceptedAccountTypesOnContractInstantiation(&vestingtypes.DelayedVestingAccount{}),
option: WithAcceptedAccountTypesOnContractInstantiation(&vestingtypes.DelayedVestingAccount{}),
account: vestingtypes.NewDelayedVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()),
@@ -689,6 +629,15 @@ func TestInstantiateWithAccounts(t *testing.T) {
sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()),
expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_001))),
},
"pruning account fails": {
option: WithAccountPruner(wasmtesting.AccountPrunerMock{CleanupExistingAccountFn: func(ctx sdk.Context, existingAccount authtypes.AccountI) (handled bool, err error) {
return false, types.ErrUnsupportedForContract.Wrap("testing")
}}),
account: vestingtypes.NewDelayedVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()),
expErr: types.ErrUnsupportedForContract,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
@@ -699,16 +648,17 @@ func TestInstantiateWithAccounts(t *testing.T) {
if !spec.initBalance.IsNil() {
keepers.Faucet.Fund(ctx, spec.account.GetAddress(), spec.initBalance)
}
if spec.acceptList != nil {
spec.acceptList.apply(keepers.WasmKeeper)
if spec.option != nil {
spec.option.apply(keepers.WasmKeeper)
}
defer func() {
if spec.acceptList != nil { // reset
if spec.option != nil { // reset
WithAcceptedAccountTypesOnContractInstantiation(&authtypes.BaseAccount{}).apply(keepers.WasmKeeper)
WithAccountPruner(NewVestingCoinBurner(keepers.BankKeeper)).apply(keepers.WasmKeeper)
}
}()
// when
gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate(ctx, 1, senderAddr, nil, initMsg, myLabel, spec.deposit)
gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate2(ctx, 1, senderAddr, nil, initMsg, myLabel, spec.deposit, mySalt, false)
if spec.expErr != nil {
assert.ErrorIs(t, gotErr, spec.expErr)
return
@@ -857,8 +807,7 @@ func TestExecute(t *testing.T) {
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
// cosmos1eycfqpgtcp4gc9g24cvg6useyncxspq8qurv2z7cs0wzcgvmffaquzwe2e build with code-id 1, DeterministicAccountAddress(t, 1) and label `demo contract 3`
require.Equal(t, "cosmos1eycfqpgtcp4gc9g24cvg6useyncxspq8qurv2z7cs0wzcgvmffaquzwe2e", addr.String())
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String())
// ensure bob doesn't exist
bobAcct := accKeeper.GetAccount(ctx, bob)
@@ -1562,7 +1511,7 @@ func TestSudo(t *testing.T) {
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos1eycfqpgtcp4gc9g24cvg6useyncxspq8qurv2z7cs0wzcgvmffaquzwe2e", addr.String())
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String())
// the community is broke
_, _, community := keyPubAddr()
@@ -2077,104 +2026,6 @@ func TestQueryIsolation(t *testing.T) {
assert.Nil(t, ctx.KVStore(k.storeKey).Get([]byte(`set_in_query`)))
}
func TestBuildContractAddress(t *testing.T) {
x, y := sdk.GetConfig().GetBech32AccountAddrPrefix(), sdk.GetConfig().GetBech32AccountPubPrefix()
t.Cleanup(func() {
sdk.GetConfig().SetBech32PrefixForAccount(x, y)
})
sdk.GetConfig().SetBech32PrefixForAccount("purple", "purple")
// test vectors from https://gist.github.com/webmaster128/e4d401d414bd0e7e6f70482f11877fbe
specs := map[string]struct {
checksum []byte
label string
creator string
expAddress string
}{
"initial account addr example": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: "instance 1",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple1jukvumwqfxapueg6un6rtmafxktcluzv70xc3edz2m5t3slgw49qrmhc03",
},
"account addr with different label": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: "instance 2",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple1jpc2w2vu2t6k7u09p6v8e3w28ptnzvvt2snu5rytlj8qya27dq5sjkwm06",
},
"initial contract addr example": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: "instance 1",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
expAddress: "purple1wde5zlcveuh79w37w5g244qu9xja3hgfulyfkjvkxvwvjzgd5l3qfraz3c",
},
"contract addr with different label": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: "instance 2",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
expAddress: "purple1vae2kf0r3ehtq5q2jmfkg7wp4ckxwrw8dv4pvazz5nlzzu05lxzq878fa9",
},
"account addr with different checksum": {
checksum: fromHex("1DA6C16DE2CBAF7AD8CBB66F0925BA33F5C278CB2491762D04658C1480EA229B"),
label: "instance 1",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple1gmgvt9levtn52mpfal3gl5tv60f47zez3wgczrh5c9352sfm9crs47zt0k",
},
"account addr with different checksum and label": {
checksum: fromHex("1DA6C16DE2CBAF7AD8CBB66F0925BA33F5C278CB2491762D04658C1480EA229B"),
label: "instance 2",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple13jrcqxknt05rhdxmegjzjel666yay6fj3xvfp6445k7a9q2km4wqa7ss34",
},
"contract addr with different checksum": {
checksum: fromHex("1DA6C16DE2CBAF7AD8CBB66F0925BA33F5C278CB2491762D04658C1480EA229B"),
label: "instance 1",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
expAddress: "purple1lu0lf6wmqeuwtrx93ptzvf4l0dyyz2vz6s8h5y9cj42fvhsmracq49pww9",
},
"contract addr with different checksum and label": {
checksum: fromHex("1DA6C16DE2CBAF7AD8CBB66F0925BA33F5C278CB2491762D04658C1480EA229B"),
label: "instance 2",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m",
expAddress: "purple1zmerc8a9ml2au29rq3knuu35fktef3akceurckr6pf370n0wku7sw3c9mj",
},
// regression tests
"min label size": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: "x",
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple16pc8gt824lmp3dh2sxvttj0ykcs02n5p3ldswhv3j7y853gghlfq7mqeh9",
},
"max label size": {
checksum: fromHex("13A1FC994CC6D1C81B746EE0C0FF6F90043875E0BF1D9BE6B7D779FC978DC2A5"),
label: strings.Repeat("x", types.MaxLabelSize),
creator: "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py",
expAddress: "purple1prkdvjmvv4s3tnppfxmlpj259v9cplf3wws4qq9qd7w3s4yqzqeqem4759",
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
creatorAddr, err := sdk.AccAddressFromBech32(spec.creator)
require.NoError(t, err)
// when
gotAddr := BuildContractAddress(spec.checksum, creatorAddr, spec.label)
require.Equal(t, spec.expAddress, gotAddr.String())
require.NoError(t, sdk.VerifyAddressFormat(gotAddr))
})
}
}
func fromHex(s string) []byte {
r, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return r
}
func TestSetAccessConfig(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper

View File

@@ -1,27 +0,0 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper *Keeper
}
// NewMigrator returns a new Migrator.
func NewMigrator(keeper *Keeper) Migrator {
return Migrator{keeper: keeper}
}
var keyLastInstanceID = append(types.SequenceKeyPrefix, []byte("lastContractId")...)
// Migrate1to2 migrates from version 1 to 2.
// Remove the unused sequence for address generation
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
store := ctx.KVStore(m.keeper.storeKey)
store.Delete(keyLastInstanceID)
return nil
}

View File

@@ -1,20 +0,0 @@
package keeper
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
)
func TestMigrateV1ToV2(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
store := ctx.KVStore(keepers.WasmKeeper.storeKey)
store.Set(keyLastInstanceID, sdk.Uint64ToBigEndian(100))
// when
NewMigrator(keepers.WasmKeeper).Migrate1to2(ctx)
// then
assert.False(t, store.Has(keyLastInstanceID))
}

View File

@@ -43,6 +43,7 @@ func (m msgServer) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*t
}, nil
}
// InstantiateContract instantiate a new contract with classic sequence based address generation
func (m msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInstantiateContract) (*types.MsgInstantiateContractResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
@@ -74,6 +75,37 @@ func (m msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInst
}, nil
}
// InstantiateContract2 instantiate a new contract with predicatable address generated
func (m msgServer) InstantiateContract2(goCtx context.Context, msg *types.MsgInstantiateContract2) (*types.MsgInstantiateContract2Response, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, sdkerrors.Wrap(err, "sender")
}
var adminAddr sdk.AccAddress
if msg.Admin != "" {
if adminAddr, err = sdk.AccAddressFromBech32(msg.Admin); err != nil {
return nil, sdkerrors.Wrap(err, "admin")
}
}
ctx.EventManager().EmitEvent(sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
))
contractAddr, data, err := m.keeper.Instantiate2(ctx, msg.CodeID, senderAddr, adminAddr, msg.Msg, msg.Label, msg.Funds, msg.Salt, msg.FixMsg)
if err != nil {
return nil, err
}
return &types.MsgInstantiateContract2Response{
Address: contractAddr.String(),
Data: data,
}, nil
}
func (m msgServer) ExecuteContract(goCtx context.Context, msg *types.MsgExecuteContract) (*types.MsgExecuteContractResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)

View File

@@ -2,25 +2,21 @@ package keeper
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"os"
"testing"
"github.com/cosmos/cosmos-sdk/x/params/client/utils"
wasmvm "github.com/CosmWasm/wasmvm"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/params/client/utils"
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
@@ -117,8 +113,9 @@ func TestInstantiateProposal(t *testing.T) {
require.NoError(t, err)
// then
codeHash := keepers.WasmKeeper.GetCodeInfo(ctx, 1).CodeHash
contractAddr := BuildContractAddress(codeHash, oneAddress, "testing")
contractAddr, err := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr")
require.NoError(t, err)
cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, cInfo)
assert.Equal(t, uint64(1), cInfo.CodeID)
@@ -188,8 +185,9 @@ func TestInstantiateProposal_NoAdmin(t *testing.T) {
require.NoError(t, err)
// then
codeHash := keepers.WasmKeeper.GetCodeInfo(ctx, 1).CodeHash
contractAddr := BuildContractAddress(codeHash, oneAddress, "testing")
contractAddr, err := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr")
require.NoError(t, err)
cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, cInfo)
assert.Equal(t, uint64(1), cInfo.CodeID)
@@ -230,7 +228,7 @@ func TestMigrateProposal(t *testing.T) {
var (
anyAddress = DeterministicAccountAddress(t, 1)
otherAddress = DeterministicAccountAddress(t, 2)
contractAddr = BuildContractAddress(codeInfoFixture.CodeHash, RandomAccountAddress(t), "")
contractAddr = BuildContractAddressClassic(1, 1)
)
contractInfoFixture := types.ContractInfoFixture(func(c *types.ContractInfo) {
@@ -407,13 +405,12 @@ func TestSudoProposal(t *testing.T) {
}
func TestAdminProposals(t *testing.T) {
var (
otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen)
contractAddr = BuildContractAddressClassic(1, 1)
)
wasmCode, err := os.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
var (
otherAddress = DeterministicAccountAddress(t, 2)
codeHash = sha256.Sum256(wasmCode)
contractAddr = BuildContractAddress(codeHash[:], RandomAccountAddress(t), "")
)
specs := map[string]struct {
state types.ContractInfo

View File

@@ -158,9 +158,8 @@ func TestQuerySmartContractState(t *testing.T) {
func TestQuerySmartContractPanics(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
creator := RandomAccountAddress(t)
contractAddr := BuildContractAddress([]byte("myCodeHash"), creator, "testing")
keepers.WasmKeeper.storeCodeInfo(ctx, 1, types.CodeInfo{CodeHash: []byte("myCodeHash")})
contractAddr := BuildContractAddressClassic(1, 1)
keepers.WasmKeeper.storeCodeInfo(ctx, 1, types.CodeInfo{})
keepers.WasmKeeper.storeContractInfo(ctx, contractAddr, &types.ContractInfo{
CodeID: 1,
Created: types.NewAbsoluteTxPosition(ctx),

View File

@@ -9,20 +9,18 @@ import (
"testing"
"time"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/std"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
@@ -72,7 +70,6 @@ import (
dbm "github.com/tendermint/tm-db"
wasmappparams "github.com/CosmWasm/wasmd/app/params"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
"github.com/CosmWasm/wasmd/x/wasm/types"
)

View File

@@ -1,14 +0,0 @@
package wasmtesting
import sdk "github.com/cosmos/cosmos-sdk/types"
type MockCoinTransferrer struct {
TransferCoinsFn func(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
}
func (m *MockCoinTransferrer) TransferCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error {
if m.TransferCoinsFn == nil {
panic("not expected to be called")
}
return m.TransferCoinsFn(ctx, fromAddr, toAddr, amt)
}

View File

@@ -0,0 +1,28 @@
package wasmtesting
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
type MockCoinTransferrer struct {
TransferCoinsFn func(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
}
func (m *MockCoinTransferrer) TransferCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error {
if m.TransferCoinsFn == nil {
panic("not expected to be called")
}
return m.TransferCoinsFn(ctx, fromAddr, toAddr, amt)
}
type AccountPrunerMock struct {
CleanupExistingAccountFn func(ctx sdk.Context, existingAccount authtypes.AccountI) (handled bool, err error)
}
func (m AccountPrunerMock) CleanupExistingAccount(ctx sdk.Context, existingAccount authtypes.AccountI) (handled bool, err error) {
if m.CleanupExistingAccountFn == nil {
panic("not expected to be called")
}
return m.CleanupExistingAccountFn(ctx, existingAccount)
}

View File

@@ -134,11 +134,6 @@ func NewAppModule(
func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(keeper.NewDefaultPermissionKeeper(am.keeper)))
types.RegisterQueryServer(cfg.QueryServer(), NewQuerier(am.keeper))
m := keeper.NewMigrator(am.keeper)
if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil {
panic(err)
}
}
func (am AppModule) LegacyQuerierHandler(amino *codec.LegacyAmino) sdk.Querier { //nolint:staticcheck

View File

@@ -7,11 +7,8 @@ import (
"os"
"testing"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/CosmWasm/wasmd/x/wasm/keeper/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/module"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
@@ -24,6 +21,7 @@ import (
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/keeper/testdata"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
@@ -148,8 +146,7 @@ type state struct {
func TestHandleInstantiate(t *testing.T) {
data := setupTest(t)
creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
data.faucet.Fund(data.ctx, creator, sdk.NewInt64Coin("denom", 100000))
creator := data.faucet.NewFundedRandomAccount(data.ctx, sdk.NewInt64Coin("denom", 100000))
h := data.module.Route().Handler()
q := data.module.LegacyQuerierHandler(nil)
@@ -183,7 +180,7 @@ func TestHandleInstantiate(t *testing.T) {
require.NoError(t, err)
contractBech32Addr := parseInitResponse(t, res.Data)
require.Equal(t, "cosmos1400ax8h2dxe8ch64sus5sczqdhwv28hpjkkaphpa83he4fhz6k4qcm9kul", contractBech32Addr)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr)
// this should be standard x/wasm init event, nothing from contract
require.Equal(t, 3, len(res.Events), prettyEvents(res.Events))
require.Equal(t, "message", res.Events[0].Type)
@@ -210,9 +207,7 @@ func TestHandleExecute(t *testing.T) {
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
data.faucet.Fund(data.ctx, creator, sdk.NewInt64Coin("denom", 100000*2))
creator := data.faucet.NewFundedRandomAccount(data.ctx, deposit.Add(deposit...)...)
fred := data.faucet.NewFundedRandomAccount(data.ctx, topUp...)
h := data.module.Route().Handler()
@@ -244,7 +239,7 @@ func TestHandleExecute(t *testing.T) {
require.NoError(t, err)
contractBech32Addr := parseInitResponse(t, res.Data)
require.Equal(t, "cosmos1400ax8h2dxe8ch64sus5sczqdhwv28hpjkkaphpa83he4fhz6k4qcm9kul", contractBech32Addr)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr)
// this should be standard x/wasm message event, init event, plus a bank send event (2), with no custom contract events
require.Equal(t, 6, len(res.Events), prettyEvents(res.Events))
require.Equal(t, "message", res.Events[0].Type)
@@ -377,7 +372,7 @@ func TestHandleExecuteEscrow(t *testing.T) {
res, err = h(data.ctx, &initCmd)
require.NoError(t, err)
contractBech32Addr := parseInitResponse(t, res.Data)
require.Equal(t, "cosmos1400ax8h2dxe8ch64sus5sczqdhwv28hpjkkaphpa83he4fhz6k4qcm9kul", contractBech32Addr)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr)
handleMsg := map[string]interface{}{
"release": map[string]interface{}{},

View File

@@ -13,6 +13,7 @@ import (
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { //nolint:staticcheck
cdc.RegisterConcrete(&MsgStoreCode{}, "wasm/MsgStoreCode", nil)
cdc.RegisterConcrete(&MsgInstantiateContract{}, "wasm/MsgInstantiateContract", nil)
cdc.RegisterConcrete(&MsgInstantiateContract2{}, "wasm/MsgInstantiateContract2", nil)
cdc.RegisterConcrete(&MsgExecuteContract{}, "wasm/MsgExecuteContract", nil)
cdc.RegisterConcrete(&MsgMigrateContract{}, "wasm/MsgMigrateContract", nil)
cdc.RegisterConcrete(&MsgUpdateAdmin{}, "wasm/MsgUpdateAdmin", nil)
@@ -35,6 +36,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
(*sdk.Msg)(nil),
&MsgStoreCode{},
&MsgInstantiateContract{},
&MsgInstantiateContract2{},
&MsgExecuteContract{},
&MsgMigrateContract{},
&MsgUpdateAdmin{},

View File

@@ -28,8 +28,27 @@ type ContractOpsKeeper interface {
// Create uploads and compiles a WASM contract, returning a short identifier for the contract
Create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *AccessConfig) (codeID uint64, checksum []byte, err error)
// Instantiate creates an instance of a WASM contract
Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error)
// Instantiate creates an instance of a WASM contract using the classic sequence based address generator
Instantiate(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
) (sdk.AccAddress, []byte, error)
// Instantiate2 creates an instance of a WASM contract using the predictable address generator
Instantiate2(
ctx sdk.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
salt []byte,
fixMsg bool,
) (sdk.AccAddress, []byte, error)
// Execute executes the contract instance
Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error)

View File

@@ -33,6 +33,7 @@ var (
TXCounterPrefix = []byte{0x08}
KeyLastCodeID = append(SequenceKeyPrefix, []byte("lastCodeId")...)
KeyLastInstanceID = append(SequenceKeyPrefix, []byte("lastContractId")...)
)
// GetCodeKey constructs the key for retreiving the ID for the WASM code

View File

@@ -306,3 +306,56 @@ func (msg MsgIBCCloseChannel) GetSignBytes() []byte {
func (msg MsgIBCCloseChannel) GetSigners() []sdk.AccAddress {
return nil
}
var _ sdk.Msg = &MsgInstantiateContract2{}
func (msg MsgInstantiateContract2) Route() string {
return RouterKey
}
func (msg MsgInstantiateContract2) Type() string {
return "instantiate2"
}
func (msg MsgInstantiateContract2) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
if msg.CodeID == 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "code id is required")
}
if err := ValidateLabel(msg.Label); err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "label is required")
}
if !msg.Funds.IsValid() {
return sdkerrors.ErrInvalidCoins
}
if len(msg.Admin) != 0 {
if _, err := sdk.AccAddressFromBech32(msg.Admin); err != nil {
return sdkerrors.Wrap(err, "admin")
}
}
if err := msg.Msg.ValidateBasic(); err != nil {
return sdkerrors.Wrap(err, "payload msg")
}
if err := ValidateSalt(msg.Salt); err != nil {
return sdkerrors.Wrap(err, "salt")
}
return nil
}
func (msg MsgInstantiateContract2) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg))
}
func (msg MsgInstantiateContract2) GetSigners() []sdk.AccAddress {
senderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil { // should never happen as valid basic rejects invalid addresses
panic(err.Error())
}
return []sdk.AccAddress{senderAddr}
}

File diff suppressed because it is too large Load Diff

View File

@@ -186,6 +186,142 @@ func TestInstantiateContractValidation(t *testing.T) {
}
}
func TestInstantiateContract2Validation(t *testing.T) {
bad, err := sdk.AccAddressFromHex("012345")
require.NoError(t, err)
badAddress := bad.String()
// proper address size
goodAddress := sdk.AccAddress(make([]byte, 20)).String()
sdk.GetConfig().SetAddressVerifier(VerifyAddressLen())
cases := map[string]struct {
msg MsgInstantiateContract2
valid bool
}{
"empty": {
msg: MsgInstantiateContract2{},
valid: false,
},
"correct minimal": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte("{}"),
Salt: []byte{0},
},
valid: true,
},
"missing code": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
Label: "foo",
Msg: []byte("{}"),
Salt: []byte{0},
},
valid: false,
},
"missing label": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
Msg: []byte("{}"),
Salt: []byte{0},
},
valid: false,
},
"label too long": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
Label: strings.Repeat("food", 33),
Salt: []byte{0},
},
valid: false,
},
"bad sender minimal": {
msg: MsgInstantiateContract2{
Sender: badAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte("{}"),
Salt: []byte{0},
},
valid: false,
},
"correct maximal": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: strings.Repeat("a", MaxLabelSize),
Msg: []byte(`{"some": "data"}`),
Funds: sdk.Coins{sdk.Coin{Denom: "foobar", Amount: sdk.NewInt(200)}},
Salt: bytes.Repeat([]byte{0}, MaxSaltSize),
FixMsg: true,
},
valid: true,
},
"negative funds": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte(`{"some": "data"}`),
// we cannot use sdk.NewCoin() constructors as they panic on creating invalid data (before we can test)
Funds: sdk.Coins{sdk.Coin{Denom: "foobar", Amount: sdk.NewInt(-200)}},
Salt: []byte{0},
},
valid: false,
},
"non json init msg": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte("invalid-json"),
Salt: []byte{0},
},
valid: false,
},
"empty init msg": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Salt: []byte{0},
},
valid: false,
},
"empty salt": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte(`{"some": "data"}`),
},
valid: false,
},
"salt too long": {
msg: MsgInstantiateContract2{
Sender: goodAddress,
CodeID: firstCodeID,
Label: "foo",
Msg: []byte(`{"some": "data"}`),
Salt: bytes.Repeat([]byte{0}, 65),
},
valid: false,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := tc.msg.ValidateBasic()
if tc.valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
func TestExecuteContractValidation(t *testing.T) {
bad, err := sdk.AccAddressFromHex("012345")
require.NoError(t, err)

View File

@@ -4,8 +4,11 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// MaxSaltSize is the longest salt that can be used when instantiating a contract
const MaxSaltSize = 64
var (
// MaxLabelSize is the longest label that can be used when Instantiating a contract
// MaxLabelSize is the longest label that can be used when instantiating a contract
MaxLabelSize = 128 // extension point for chains to customize via compile flag.
// MaxWasmSize is the largest a compiled contract code can be when storing code on chain
@@ -28,7 +31,18 @@ func ValidateLabel(label string) error {
return sdkerrors.Wrap(ErrEmpty, "is required")
}
if len(label) > MaxLabelSize {
return sdkerrors.Wrap(ErrLimit, "cannot be longer than 128 characters")
return ErrLimit.Wrapf("cannot be longer than %d characters", MaxLabelSize)
}
return nil
}
// ValidateSalt ensure salt constraints
func ValidateSalt(salt []byte) error {
switch n := len(salt); {
case n == 0:
return sdkerrors.Wrap(ErrEmpty, "is required")
case n > MaxSaltSize:
return ErrLimit.Wrapf("cannot be longer than %d characters", MaxSaltSize)
}
return nil
}