diff --git a/go.mod b/go.mod index 19689fa7f..e925b5eaa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/99designs/keyring v1.1.6 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d - github.com/CosmWasm/wasmvm v1.0.0 + github.com/CosmWasm/wasmvm v1.2.4 github.com/VictoriaMetrics/fastcache v1.8.0 github.com/Workiva/go-datastructures v1.0.53 github.com/alicebob/miniredis/v2 v2.17.0 @@ -73,7 +73,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/btcd v0.1.1 github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 @@ -179,6 +179,7 @@ require ( ) replace ( + github.com/CosmWasm/wasmvm => github.com/okx/wasmvm v1.2.4-0.20230726040355-85ad18b1d9d1 github.com/buger/jsonparser => github.com/buger/jsonparser v1.0.0 // imported by nacos-go-sdk, upgraded to v1.0.0 in case of a known vulnerable bug github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 github.com/cosmos/gorocksdb => github.com/okx/grocksdb v1.6.45-okc2 diff --git a/go.sum b/go.sum index 0af381cf6..47318158e 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmvm v1.0.0 h1:NRmnHe3xXsKn2uEcB1F5Ha323JVAhON+BI6L177dlKc= -github.com/CosmWasm/wasmvm v1.0.0/go.mod h1:ei0xpvomwSdONsxDuONzV7bL1jSET1M8brEx0FCXc+A= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= @@ -636,6 +634,8 @@ github.com/okx/go-ethereum v1.10.26-0.20230605041300-9db59c1d91e6 h1:pXsdHnWHejH github.com/okx/go-ethereum v1.10.26-0.20230605041300-9db59c1d91e6/go.mod h1:f9/CleoGkiU7NWF9tdpWGfEYmFbN7zUi17jGick31KM= github.com/okx/grocksdb v1.6.45-okc2 h1:Dbmax+uY71YuGTkJftpF55dbGW7hfIVOfNz2mnFqXl0= github.com/okx/grocksdb v1.6.45-okc2/go.mod h1:+/BHUY+mT0tbaVXwO2wTtD9eytazyw1W5n2O7AGyXZA= +github.com/okx/wasmvm v1.2.4-0.20230726040355-85ad18b1d9d1 h1:GkLSSyFqizXMbWcjK4xORA1vwceBP7fkKRIKwyixCb4= +github.com/okx/wasmvm v1.2.4-0.20230726040355-85ad18b1d9d1/go.mod h1:vW/E3h8j9xBQs9bCoijDuawKo9kCtxOaS8N8J7KFtkc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -799,8 +799,9 @@ github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUW github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -810,8 +811,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= diff --git a/libs/cosmos-sdk/types/context.go b/libs/cosmos-sdk/types/context.go index 744903119..74f391f19 100644 --- a/libs/cosmos-sdk/types/context.go +++ b/libs/cosmos-sdk/types/context.go @@ -49,6 +49,7 @@ type Context struct { accountCache *AccountCache paraMsg *ParaMsg // txCount uint32 + wasmCallDepth uint32 overridesBytes []byte // overridesBytes is used to save overrides info, passed from ethCall to x/evm watcher *TxWatcher feesplitInfo *FeeSplitInfo @@ -416,6 +417,18 @@ func (c *Context) SetWatcher(w IWatcher) { c.watcher.IWatcher = w } +func (c *Context) IncrementCallDepth() { + c.wasmCallDepth++ +} + +func (c *Context) DecrementCallDepth() { + c.wasmCallDepth-- +} + +func (c *Context) CallDepth() uint32 { + return c.wasmCallDepth +} + func (c *Context) GetWatcher() IWatcher { if c.watcher == nil { return emptyWatcher diff --git a/x/wasm/keeper/api.go b/x/wasm/keeper/api.go index 15cc1317d..fe22db651 100644 --- a/x/wasm/keeper/api.go +++ b/x/wasm/keeper/api.go @@ -4,6 +4,9 @@ import ( wasmvm "github.com/CosmWasm/wasmvm" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/okx/okbchain/libs/cosmos-sdk/types" + sdkerrors "github.com/okx/okbchain/libs/cosmos-sdk/types/errors" + "github.com/okx/okbchain/x/wasm/types" + "strconv" ) const ( @@ -14,6 +17,8 @@ const ( // DefaultDeserializationCostPerByte The formular should be `len(data) * deserializationCostPerByte` DefaultDeserializationCostPerByte = 1 + + CallCreateDepth = 20 ) var ( @@ -41,3 +46,40 @@ var cosmwasmAPI = wasmvm.GoAPI{ HumanAddress: humanAddress, CanonicalAddress: canonicalAddress, } + +func contractExternal(ctx sdk.Context, k Keeper) func(request wasmvmtypes.ContractCreateRequest, gasLimit uint64) (string, uint64, error) { + return func(request wasmvmtypes.ContractCreateRequest, gasLimit uint64) (string, uint64, error) { + ctx.IncrementCallDepth() + if ctx.CallDepth() >= CallCreateDepth { + return "", 0, sdkerrors.Wrap(types.ErrExceedCallDepth, strconv.Itoa(int(ctx.CallDepth()))) + } + + gasMeter := ctx.GasMeter() + ctx.SetGasMeter(sdk.NewGasMeter(k.gasRegister.FromWasmVMGas(gasLimit))) + gasBefore := ctx.GasMeter().GasConsumed() + + defer func() { + ctx.DecrementCallDepth() + + // reset gas meter + gasCost := ctx.GasMeter().GasConsumed() - gasBefore + ctx.SetGasMeter(gasMeter) + ctx.GasMeter().ConsumeGas(gasCost, "contract sub-create") + }() + + creator, err := sdk.WasmAddressFromBech32(request.Creator) + if err != nil { + return "", 0, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.Creator) + } + admin, err := sdk.WasmAddressFromBech32(request.AdminAddr) + if err != nil { + return "", 0, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.AdminAddr) + } + addr, _, err := k.CreateByContract(ctx, creator, request.WasmCode, request.CodeID, request.InitMsg, admin, request.Label, request.IsCreate2, request.Salt, nil) + if err != nil { + return "", k.gasRegister.ToWasmVMGas(ctx.GasMeter().GasConsumed() - gasBefore), err + } + + return addr.String(), k.gasRegister.ToWasmVMGas(ctx.GasMeter().GasConsumed() - gasBefore), nil + } +} diff --git a/x/wasm/keeper/contract_keeper.go b/x/wasm/keeper/contract_keeper.go index 97109e53c..39abcac4b 100644 --- a/x/wasm/keeper/contract_keeper.go +++ b/x/wasm/keeper/contract_keeper.go @@ -10,8 +10,8 @@ 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.WasmAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, err error) - instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.WasmAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.WasmAddress, []byte, error) + create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, codeHash []byte, err error) + instantiate(ctx sdk.Context, codeID uint64, creator, admin, contractAddress sdk.WasmAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.WasmAddress, []byte, error) migrate(ctx sdk.Context, contractAddress sdk.WasmAddress, caller sdk.WasmAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.WasmAddress, authZ AuthorizationPolicy) error pinCode(ctx sdk.Context, codeID uint64) error @@ -47,11 +47,12 @@ func NewDefaultPermissionKeeper(nested decoratedKeeper) *PermissionedKeeper { } func (p PermissionedKeeper) Create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte, instantiateAccess *types.AccessConfig) (codeID uint64, err error) { - return p.nested.create(ctx, creator, wasmCode, instantiateAccess, p.authZPolicy) + codeID, _, err = p.nested.create(ctx, creator, wasmCode, instantiateAccess, p.authZPolicy) + return } func (p PermissionedKeeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.WasmAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.WasmAddress, []byte, error) { - return p.nested.instantiate(ctx, codeID, creator, admin, initMsg, label, deposit, p.authZPolicy) + return p.nested.instantiate(ctx, codeID, creator, admin, nil, initMsg, label, deposit, p.authZPolicy) } func (p PermissionedKeeper) Execute(ctx sdk.Context, contractAddress sdk.WasmAddress, caller sdk.WasmAddress, msg []byte, coins sdk.Coins) ([]byte, error) { diff --git a/x/wasm/keeper/cross_contract_call.go b/x/wasm/keeper/cross_contract_call.go new file mode 100644 index 000000000..a793f5b4a --- /dev/null +++ b/x/wasm/keeper/cross_contract_call.go @@ -0,0 +1,87 @@ +package keeper + +import ( + "encoding/json" + wasmvm "github.com/CosmWasm/wasmvm" + sdk "github.com/okx/okbchain/libs/cosmos-sdk/types" +) + +var ( + wasmCache wasmvm.Cache +) + +func SetWasmCache(cache wasmvm.Cache) { + wasmCache = cache +} + +func GetWasmCacheInfo() wasmvm.Cache { + return wasmCache +} + +func getCallerInfoFunc(ctx sdk.Context, keeper Keeper) func(contractAddress, storeAddress string) ([]byte, uint64, wasmvm.KVStore, wasmvm.Querier, wasmvm.GasMeter, error) { + return func(contractAddress, storeAddress string) ([]byte, uint64, wasmvm.KVStore, wasmvm.Querier, wasmvm.GasMeter, error) { + gasBefore := ctx.GasMeter().GasConsumed() + codeHash, store, querier, gasMeter, err := getCallerInfo(ctx, keeper, contractAddress, storeAddress) + gasAfter := ctx.GasMeter().GasConsumed() + return codeHash, keeper.gasRegister.ToWasmVMGas(gasAfter - gasBefore), store, querier, gasMeter, err + } +} + +func getCallerInfo(ctx sdk.Context, keeper Keeper, contractAddress, storeAddress string) ([]byte, wasmvm.KVStore, wasmvm.Querier, wasmvm.GasMeter, error) { + cAddr, err := sdk.WasmAddressFromBech32(contractAddress) + if err != nil { + return nil, nil, nil, nil, err + } + // 1. get wasm code from contractAddress + _, codeInfo, prefixStore, err := keeper.contractInstance(ctx, cAddr) + if err != nil { + return nil, nil, nil, nil, err + } + // 2. contractAddress == storeAddress and direct return + if contractAddress == storeAddress { + queryHandler := keeper.newQueryHandler(ctx, cAddr) + return codeInfo.CodeHash, prefixStore, queryHandler, keeper.gasMeter(ctx), nil + } + // 3. get store from storeaddress + sAddr, err := sdk.WasmAddressFromBech32(storeAddress) + if err != nil { + return nil, nil, nil, nil, err + } + _, _, prefixStore, err = keeper.contractInstance(ctx, sAddr) + if err != nil { + return nil, nil, nil, nil, err + } + queryHandler := keeper.newQueryHandler(ctx, sAddr) + return codeInfo.CodeHash, prefixStore, queryHandler, keeper.gasMeter(ctx), nil +} + +func transferCoinsFunc(ctx sdk.Context, keeper Keeper) func(contractAddress, caller string, coinsData []byte) (uint64, error) { + return func(contractAddress, caller string, coinsData []byte) (uint64, error) { + var coins sdk.Coins + err := json.Unmarshal(coinsData, &coins) + if err != nil { + return 0, err + } + gasBefore := ctx.GasMeter().GasConsumed() + err = transferCoins(ctx, keeper, contractAddress, caller, coins) + gasAfter := ctx.GasMeter().GasConsumed() + return keeper.gasRegister.ToWasmVMGas(gasAfter - gasBefore), err + } +} + +func transferCoins(ctx sdk.Context, keeper Keeper, contractAddress, caller string, coins sdk.Coins) error { + if !coins.IsZero() { + contractAddr, err := sdk.WasmAddressFromBech32(contractAddress) + if err != nil { + return err + } + callerAddr, err := sdk.WasmAddressFromBech32(caller) + if err != nil { + return err + } + if err := keeper.bank.TransferCoins(ctx, callerAddr, contractAddr, coins); err != nil { + return err + } + } + return nil +} diff --git a/x/wasm/keeper/cross_contract_call_test.go b/x/wasm/keeper/cross_contract_call_test.go new file mode 100644 index 000000000..06329e4fb --- /dev/null +++ b/x/wasm/keeper/cross_contract_call_test.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "encoding/json" + sdk "github.com/okx/okbchain/libs/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "testing" +) + +func TestGetWasmCallInfo(t *testing.T) { + ctx, keepers := CreateTestInput(t, false, SupportedFeatures) + keeper := keepers.ContractKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) + + codeID, err := keeper.Create(ctx, creator, hackatomWasm, nil) + require.NoError(t, err) + + _, _, bob := keyPubAddr() + _, _, fred := keyPubAddr() + + initMsg := HackatomExampleInitMsg{ + Verifier: fred, + Beneficiary: bob, + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + em := sdk.NewEventManager() + // create with no balance is also legal + ctx.SetEventManager(em) + gotContractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, "demo contract 1", nil) + require.NoError(t, err) + + wasmkeeper := *keepers.WasmKeeper + // 1. contractAddress is equal to storeAddress + _, _, _, _, err = getCallerInfo(ctx, wasmkeeper, gotContractAddr.String(), gotContractAddr.String()) + require.NoError(t, err) + + // 2. contractAddress is not exist + _, _, _, _, err = getCallerInfo(ctx, wasmkeeper, "0xE70e7466a2f18FAd8C97c45Ba8fEc57d90F3435E", "0xE70e7466a2f18FAd8C97c45Ba8fEc57d90F3435E") + require.NotNil(t, err) + + // 3. storeAddress is not exist + _, _, _, _, err = getCallerInfo(ctx, wasmkeeper, gotContractAddr.String(), "0xE70e7466a2f18FAd8C97c45Ba8fEc57d90F3435E") + require.NotNil(t, err) + + // 4. contractAddress is not equal to storeAddress + gotContractAddr2, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, "demo contract 1", nil) + _, kvs, q, _, err := getCallerInfo(ctx, wasmkeeper, gotContractAddr.String(), gotContractAddr2.String()) + require.NoError(t, err) + require.NotNil(t, kvs) + require.NotNil(t, q) +} diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 98c24c742..6b71acada 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -217,6 +217,10 @@ func newKeeper(cdc *codec.CodecProxy, } // not updateable, yet keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper)) + + // register + wasmvm.RegisterGetWasmCacheInfo(GetWasmCacheInfo) + SetWasmCache(wasmer.GetCache()) return *keeper } @@ -378,13 +382,36 @@ func (k Keeper) OnAccountUpdated(acc exported.Account) { watcher.DeleteAccount(sdk.AccToAWasmddress(acc.GetAddress())) } -func (k Keeper) create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, err error) { +// CreateByContract create the smart contract from other contract. +func (k Keeper) CreateByContract(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte, codeID uint64, initMsg []byte, adminAddr sdk.WasmAddress, label string, isCreate2 bool, salt []byte, deposit sdk.Coins) (sdk.WasmAddress, []byte, error) { + var codeHash []byte + var err error + if codeID < 1 { + codeID, codeHash, err = k.create(ctx, creator, wasmCode, nil, DefaultAuthorizationPolicy{}) + if err != nil { + return nil, nil, err + } + } + if codeID >= 1 && isCreate2 { + codeInfo := k.GetCodeInfo(ctx, codeID) + codeHash = codeInfo.CodeHash + } + + var contractAddress sdk.WasmAddress + if isCreate2 { + contractAddress = BuildContractAddressPredictable(codeHash, creator, salt, []byte{}) + } + + return k.instantiate(ctx, codeID, creator, adminAddr, contractAddress, initMsg, label, deposit, DefaultAuthorizationPolicy{}) +} + +func (k Keeper) create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, codeHash []byte, err error) { if creator == nil { - return 0, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil") + return 0, nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil") } if !authZ.CanCreateCode(k.getUploadAccessConfig(ctx), creator) { - return 0, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, types.GenerateUnauthorizeError(k.GetParams(ctx).CodeUploadAccess.Permission)) + return 0, nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, types.GenerateUnauthorizeError(k.GetParams(ctx).CodeUploadAccess.Permission)) } // figure out proper instantiate access defaultAccessConfig := k.getInstantiateAccessConfig(ctx).With(creator) @@ -392,28 +419,28 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte instantiateAccess = &defaultAccessConfig } else if !instantiateAccess.IsSubset(defaultAccessConfig) { // we enforce this must be subset of default upload access - return 0, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "instantiate access must be subset of default upload access") + return 0, nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "instantiate access must be subset of default upload access") } wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) if err != nil { - return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + return 0, nil, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } ctx.GasMeter().ConsumeGas(k.gasRegister.CompileCosts(len(wasmCode)), "Compiling WASM Bytecode") checksum, err := k.wasmVM.Create(wasmCode) if err != nil { - return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + return 0, nil, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } report, err := k.wasmVM.AnalyzeCode(checksum) if err != nil { - return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + return 0, nil, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } codeID = k.autoIncrementID(ctx, types.KeyLastCodeID) k.Logger(ctx).Debug("storing new contract", "features", report.RequiredFeatures, "code_id", codeID) result, err := types.ConvertAccessConfig(*instantiateAccess) if err != nil { - return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + return 0, nil, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } codeInfo := types.NewCodeInfo(checksum, creator, result) k.storeCodeInfo(ctx, codeID, codeInfo) @@ -427,7 +454,7 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.WasmAddress, wasmCode []byte } ctx.EventManager().EmitEvent(evt) - return codeID, nil + return codeID, checksum, nil } func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo) { @@ -460,7 +487,7 @@ 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.WasmAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.WasmAddress, []byte, error) { +func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin, contractAddress sdk.WasmAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.WasmAddress, []byte, error) { //defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate") // This method does not support parallel execution. if ctx.ParaMsg() != nil { @@ -470,7 +497,9 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.W ctx.GasMeter().ConsumeGas(instanceCosts, "Loading CosmWasm module: instantiate") // create contract address - contractAddress := k.generateContractAddress(ctx, codeID) + if contractAddress.Empty() { + contractAddress = k.generateContractAddress(ctx, codeID) + } existingAcct := k.accountKeeper.GetAccount(ctx, sdk.WasmToAccAddress(contractAddress)) if existingAcct != nil { return nil, nil, sdkerrors.Wrap(types.ErrAccountExists, existingAcct.GetAddress().String()) @@ -502,6 +531,13 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.W env := types.NewEnv(ctx, contractAddress) adapters := sdk.CoinsToCoinAdapters(deposit) info := types.NewInfo(creator, adapters) + cosmwasmAPI := wasmvm.GoAPI{ + HumanAddress: humanAddress, + CanonicalAddress: canonicalAddress, + Contract: contractExternal(ctx, k), + GetCallInfo: getCallerInfoFunc(ctx, k), + TransferCoins: transferCoinsFunc(ctx, k), + } // create prefixed data store // 0x00 | BuildContractAddress (sdk.WasmAddress) | stateRoot @@ -581,6 +617,13 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.WasmAddress, caller env := types.NewEnv(ctx, contractAddress) adapters := sdk.CoinsToCoinAdapters(coins) info := types.NewInfo(caller, adapters) + cosmwasmAPI := wasmvm.GoAPI{ + HumanAddress: humanAddress, + CanonicalAddress: canonicalAddress, + Contract: contractExternal(ctx, k), + GetCallInfo: getCallerInfoFunc(ctx, k), + TransferCoins: transferCoinsFunc(ctx, k), + } // prepare querier querier := k.newQueryHandler(ctx, contractAddress) @@ -659,7 +702,6 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.WasmAddress, caller } env := types.NewEnv(ctx, contractAddress) - // prepare querier querier := k.newQueryHandler(ctx, contractAddress) @@ -713,7 +755,6 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.WasmAddress, msg []byt ctx.GasMeter().ConsumeGas(sudoSetupCosts, "Loading CosmWasm module: sudo") env := types.NewEnv(ctx, contractAddress) - // prepare querier querier := k.newQueryHandler(ctx, contractAddress) gas := k.runtimeGasForContract(ctx) @@ -748,7 +789,6 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.WasmAddress, reply wa ctx.GasMeter().ConsumeGas(replyCosts, "Loading CosmWasm module: reply") env := types.NewEnv(ctx, contractAddress) - // prepare querier querier := k.newQueryHandler(ctx, contractAddress) gas := k.runtimeGasForContract(ctx) @@ -891,6 +931,7 @@ func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.WasmAddress, req [] querier := k.newQueryHandler(ctx, contractAddr) env := types.NewEnv(ctx, contractAddr) + queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), k.runtimeGasForContract(ctx), costJSONDeserialization) k.consumeRuntimeGas(ctx, gasUsed) if qErr != nil { @@ -1214,7 +1255,7 @@ func (k Keeper) generateContractAddress(ctx sdk.Context, codeID uint64) sdk.Wasm return BuildContractAddress(codeID, instanceID) } -// BuildContractAddress builds an sdk account address for a contract. +// BuildContractAddress builds a sdk account address for a contract. func BuildContractAddress(codeID, instanceID uint64) sdk.WasmAddress { contractID := make([]byte, 16) binary.BigEndian.PutUint64(contractID[:8], codeID) @@ -1222,6 +1263,42 @@ func BuildContractAddress(codeID, instanceID uint64) sdk.WasmAddress { return types.Module(types.ModuleName, contractID)[types.ContractIndex:] } +// 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.WasmAddress, salt, initMsg types.RawContractMessage) sdk.WasmAddress { + 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 types.Module(types.ModuleName, key)[types.ContractIndex:] +} + +// UInt64LengthPrefix prepend big endian encoded byte length +func UInt64LengthPrefix(bz []byte) []byte { + return append(sdk.Uint64ToBigEndian(uint64(len(bz))), bz...) +} + func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { store := k.ada.NewStore(ctx, k.storeKey, nil) diff --git a/x/wasm/types/errors.go b/x/wasm/types/errors.go index 993d16133..6cab09775 100644 --- a/x/wasm/types/errors.go +++ b/x/wasm/types/errors.go @@ -96,6 +96,7 @@ var ( ErrUnknownExtraProposalAction = sdkErrors.Register(DefaultCodespace, 31, "extra proposal's action unknown") ErrProposerMustBeValidator = sdkErrors.Register(DefaultCodespace, 32, "the proposal of proposer must be validator") + ErrExceedCallDepth = sdkErrors.Register(DefaultCodespace, 33, "max call depth exceeded") ) type ErrNoSuchContract struct { diff --git a/x/wasm/types/store_adapter.go b/x/wasm/types/store_adapter.go index b14826379..7b48e96da 100644 --- a/x/wasm/types/store_adapter.go +++ b/x/wasm/types/store_adapter.go @@ -3,8 +3,8 @@ package types import ( "fmt" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/okx/okbchain/libs/cosmos-sdk/types" - dbm "github.com/tendermint/tm-db" ) type StoreAdapter struct { @@ -30,7 +30,7 @@ func (sa StoreAdapter) Delete(key []byte) { // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. // To iterate over entire domain, use store.Iterator(nil, nil) -func (sa StoreAdapter) Iterator(start, end []byte) dbm.Iterator { +func (sa StoreAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { iter := sa.parent.Iterator(start, end) adapter := newIteratorAdapter(iter) return adapter @@ -39,7 +39,7 @@ func (sa StoreAdapter) Iterator(start, end []byte) dbm.Iterator { // Iterator over a domain of keys in descending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. -func (sa StoreAdapter) ReverseIterator(start, end []byte) dbm.Iterator { +func (sa StoreAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { iter := sa.parent.ReverseIterator(start, end) adapter := newIteratorAdapter(iter) return adapter diff --git a/x/wasm/types/validation.go b/x/wasm/types/validation.go index 2246674f1..921462782 100644 --- a/x/wasm/types/validation.go +++ b/x/wasm/types/validation.go @@ -4,6 +4,9 @@ import ( sdkerrors "github.com/okx/okbchain/libs/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 = 128 // extension point for chains to customize via compile flag. @@ -31,3 +34,14 @@ func validateLabel(label string) error { } 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 sdkerrors.Wrapf(ErrLimit, "cannot be longer than %d characters", MaxSaltSize) + } + return nil +}