diff --git a/chain33.fork.toml b/chain33.fork.toml index e26dcf12aa..981c81300d 100644 --- a/chain33.fork.toml +++ b/chain33.fork.toml @@ -150,6 +150,7 @@ ForkCollateralizeTableUpdate=0 ForkCollateralizePrecision=0 [fork.sub.jsvm] +ForkJSFixAddrNormalize=0 Enable=0 [fork.sub.lottery] @@ -237,6 +238,7 @@ Enable=0 Enable=0 [fork.sub.wasm] +ForkWasmFixAddrNormalize=0 Enable=0 diff --git a/chain33.para.toml b/chain33.para.toml index e877dbd938..b535ce5235 100644 --- a/chain33.para.toml +++ b/chain33.para.toml @@ -457,6 +457,7 @@ Enable=-1 Enable=0 [fork.sub.wasm] +ForkWasmFixAddrNormalize=0 Enable=0 [fork.sub.valnode] @@ -489,6 +490,7 @@ ForkAutonomyDelRule=0 ForkAutonomyEnableItem=0 [fork.sub.jsvm] +ForkJSFixAddrNormalize=0 Enable=0 [fork.sub.evmxgo] diff --git a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml index 7588e667dd..3cadde22f6 100644 --- a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml +++ b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml @@ -647,6 +647,7 @@ ForkCollateralizeTableUpdate=0 ForkCollateralizePrecision=0 [fork.sub.jsvm] +ForkJSFixAddrNormalize=0 Enable=0 [fork.sub.lottery] @@ -735,6 +736,7 @@ Enable=0 Enable=0 [fork.sub.wasm] +ForkWasmFixAddrNormalize=0 Enable=0 [fork.sub.lightclient] diff --git a/plugin/dapp/js/executor/account.go b/plugin/dapp/js/executor/account.go index 85586a2dd2..fecdd10a26 100644 --- a/plugin/dapp/js/executor/account.go +++ b/plugin/dapp/js/executor/account.go @@ -4,6 +4,8 @@ import ( "github.com/33cn/chain33/account" "github.com/33cn/chain33/common/address" "github.com/33cn/chain33/types" + "github.com/33cn/plugin/plugin/dapp/common" + ptypes "github.com/33cn/plugin/plugin/dapp/js/types" "github.com/robertkrimen/otto" ) @@ -402,6 +404,13 @@ func (u *js) execTransferFunc(vm *otto.Otto) { if err := address.CheckAddress(to, u.GetHeight()); err != nil { return errReturn(vm, err) } + // 分叉后对 from/to 做 eth 地址归一化(与存储 key 格式一致), 防止大小写变体 + // 绕过 account 层 from==to 自我转账检查造成造币 + cfg := u.GetAPI().GetConfig() + if cfg.IsDappFork(u.GetHeight(), ptypes.JsX, ptypes.ForkJSFixAddrNormalize) { + from = common.FmtEthAddressWithFork(from, cfg, u.GetHeight()) + to = common.FmtEthAddressWithFork(to, cfg, u.GetHeight()) + } amount, err := call.Argument(4).ToInteger() if err != nil { return errReturn(vm, err) diff --git a/plugin/dapp/js/executor/vuln_fix_test.go b/plugin/dapp/js/executor/vuln_fix_test.go new file mode 100644 index 0000000000..25a0bf165d --- /dev/null +++ b/plugin/dapp/js/executor/vuln_fix_test.go @@ -0,0 +1,178 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package executor + +// 安全漏洞修复回归测试(executor 级): +// js dapp 的 exec_transfer 绑定(account.go execTransferFunc)此前对合约传入的 +// from/to 仅做 address.CheckAddress 校验, 不做 eth 地址归一化。account 层 +// ExecTransfer 用原始字符串比较 from==to 做自我转账检查, 而存储 key 经 +// address.FormatAddrKey 归一化为小写, 导致同一 eth 地址的大小写变体可绕过 +// 自我转账检查, 同 key 覆盖后余额不减反增, 凭空造币。 +// 修复: 分叉 ForkJSFixAddrNormalize 开启后, execTransferFunc 在调用 account 层前 +// 先用 common.FmtEthAddressWithFork 将 from/to 归一化, 使大小写变体收敛为同一字符串, +// account 层 from==to 检查必然命中, 返回 ErrSendSameToRecv。 + +import ( + "encoding/json" + "strings" + "testing" + "time" + + "github.com/33cn/chain33/account" + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/common/db" + "github.com/33cn/chain33/rpc/grpcclient" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + ptypes "github.com/33cn/plugin/plugin/dapp/js/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// 测试用 eth 地址: lower 与 mixed 为同一地址的两种大小写形式 +const ( + vulnFixJSLowerAddr = "0x1a9a5a0bbe37a15e0cf2bd00b65a4a5ce07a3d01" + vulnFixJSMixedAddr = "0x1A9A5a0bbe37a15E0cf2bd00b65a4a5ce07a3d01" + vulnFixJSOtherAddr = "0x2b8c1e6f4d3a2b1c0f9e8d7c6b5a493827160504" +) + +var vulnFixJSCode = ` +function Init(context) { + this.kvc = new kvcreator("init") + this.context = context + return this.kvc.receipt() +} + +function ExecInit() { + this.acc = new account(this.kvc, "coins", "bty") +} + +Exec.prototype.mint = function(args) { + var err = this.acc.execTransfer(this.name, args.from, args.to, args.amount) + throwerr(err, "execTransfer") + return this.kvc.receipt() +} +` + +// newVulnFixJSConfig 构造启用 eth 地址的测试配置, forkHeight<=0 时分叉取默认注册高度(0, 始终生效) +func newVulnFixJSConfig(forkHeight int64) *types.Chain33Config { + //Title=local 的配置所有分叉强制为高度0(needSetForkZero), 无法测试分叉前行为, + //参照 wasm_test.go 将 Title 替换为 chain33, 使 SetDappFork 生效 + cfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1)) + //默认配置中 eth 地址驱动禁用([address.enableHeight] eth=-2), 测试启用 eth 地址, + //模拟支持 eth 地址格式的链(如开启 EVM 的链) + cfg.GetModuleConfig().Address.EnableHeight["eth"] = 0 + if forkHeight > 0 { + cfg.SetDappFork(ptypes.JsX, ptypes.ForkJSFixAddrNormalize, forkHeight) + } + //地址驱动启用高度为全局配置, 需显式生效 + address.Init(cfg.GetModuleConfig().Address) + return cfg +} + +// initVulnFixJSExec 部署测试合约, 与 jsvm_test.go 的 initExec 逻辑一致, 但使用自定义配置 +func initVulnFixJSExec(t *testing.T, cfg *types.Chain33Config, ldb db.DB, kvdb db.KVDB) *js { + Init(ptypes.JsX, cfg, nil) + + e := newjs().(*js) + e.SetEnv(1, time.Now().Unix(), 1) + mockapi := &mocks.QueueProtocolAPI{} + mockapi.On("Query", "ticket", "RandNumHash", mock.Anything).Return(&types.ReplyHash{Hash: []byte("hello")}, nil) + mockapi.On("GetConfig", mock.Anything).Return(cfg, nil) + e.SetAPI(mockapi) + gclient, err := grpcclient.NewMainChainClient(cfg, "") + require.Nil(t, err) + e.SetExecutorAPI(mockapi, gclient) + e.SetLocalDB(kvdb) + e.SetStateDB(kvdb) + //合约名需全局唯一: 包级 codecache 以合约名为 key 缓存 VM, 与其他测试重名会加载到错误代码 + c, tx := createCodeTx("vulnfixmint", vulnFixJSCode) + + // set config key + item := &types.ConfigItem{ + Key: "mavl-manage-js-creator", + Addr: tx.From(), + Value: &types.ConfigItem_Arr{ + Arr: &types.ArrayConfig{Value: []string{tx.From()}}, + }, + } + kvdb.Set([]byte(item.Key), types.Encode(item)) + + receipt, err := e.Exec_Create(c, tx, 0) + require.Nil(t, err) + util.SaveKVList(ldb, receipt.KV) + return e +} + +// vulnFixJSFundExec 向合约执行器账户充值(amount 同时作为主账户初始余额) +func vulnFixJSFundExec(t *testing.T, cfg *types.Chain33Config, kvdb db.KVDB, execer, addr string, amount int64) *account.DB { + acc, err := account.NewAccountDB(cfg, "coins", "bty", kvdb) + require.Nil(t, err) + acc.SaveAccount(&types.Account{Balance: amount, Addr: addr}) + _, err = acc.TransferToExec(addr, address.ExecAddress(execer), amount) + require.Nil(t, err) + return acc +} + +func vulnFixJSCallMint(t *testing.T, e *js, from, to string, amount int64) error { + args, err := json.Marshal(map[string]interface{}{"from": from, "to": to, "amount": amount}) + require.Nil(t, err) + call, tx := callCodeTx("vulnfixmint", "mint", string(args)) + _, err = e.Exec_Call(call, tx, 0) + return err +} + +func TestVulnFixJSExecTransferAddrNormalize(t *testing.T) { + userExec := "user." + ptypes.JsX + ".vulnfixmint" + execAddr := address.ExecAddress(userExec) + amount := types.DefaultCoinPrecision + + //分叉后: 同一 eth 地址的大小写变体自我转账被归一化后命中 from==to 检查, 交易报错且余额不变 + t.Run("post-fork reject mixed-case self transfer", func(t *testing.T) { + dir, ldb, kvdb := util.CreateTestDB() + defer util.CloseTestDB(dir, ldb) + cfg := newVulnFixJSConfig(0) + e := initVulnFixJSExec(t, cfg, ldb, kvdb) + acc := vulnFixJSFundExec(t, cfg, kvdb, userExec, vulnFixJSLowerAddr, amount) + + err := vulnFixJSCallMint(t, e, vulnFixJSLowerAddr, vulnFixJSMixedAddr, amount) + require.NotNil(t, err, "大小写变体自我转账应被拒绝") + require.True(t, strings.Contains(err.Error(), types.ErrSendSameToRecv.Error()), + "归一化后应命中 account 层自我转账检查, 实际错误: %v", err) + require.Equal(t, amount, acc.LoadExecAccount(vulnFixJSLowerAddr, execAddr).Balance, "余额不变, 未造币") + require.Equal(t, amount, acc.LoadExecAccount(vulnFixJSMixedAddr, execAddr).Balance, "归一化后同一账户, 余额不变") + }) + + //分叉后: 正常的不同地址转账不受影响, 混合大小写的 to 归一化后入账到同一账户 + t.Run("post-fork normal transfer unaffected", func(t *testing.T) { + dir, ldb, kvdb := util.CreateTestDB() + defer util.CloseTestDB(dir, ldb) + cfg := newVulnFixJSConfig(0) + e := initVulnFixJSExec(t, cfg, ldb, kvdb) + acc := vulnFixJSFundExec(t, cfg, kvdb, userExec, vulnFixJSLowerAddr, amount) + + otherMixed := "0x2B8c1e6f4d3a2b1c0f9e8d7c6b5a493827160504" + err := vulnFixJSCallMint(t, e, vulnFixJSLowerAddr, otherMixed, amount/2) + require.Nil(t, err) + require.Equal(t, amount/2, acc.LoadExecAccount(vulnFixJSLowerAddr, execAddr).Balance) + require.Equal(t, amount/2, acc.LoadExecAccount(vulnFixJSOtherAddr, execAddr).Balance) + }) + + //分叉前: 保持原行为(不做归一化), 大小写变体自我转账仍可执行, 保证链上共识兼容 + t.Run("pre-fork behavior unchanged", func(t *testing.T) { + dir, ldb, kvdb := util.CreateTestDB() + defer util.CloseTestDB(dir, ldb) + cfg := newVulnFixJSConfig(1000000) + require.False(t, cfg.IsDappFork(1, ptypes.JsX, ptypes.ForkJSFixAddrNormalize)) + e := initVulnFixJSExec(t, cfg, ldb, kvdb) + acc := vulnFixJSFundExec(t, cfg, kvdb, userExec, vulnFixJSLowerAddr, amount) + + err := vulnFixJSCallMint(t, e, vulnFixJSLowerAddr, vulnFixJSMixedAddr, amount) + require.Nil(t, err, "分叉前保持原行为, 不做归一化") + require.Equal(t, 2*amount, acc.LoadExecAccount(vulnFixJSLowerAddr, execAddr).Balance, + "分叉前 from==to 字符串检查被绕过, 同 key 覆盖后余额翻倍(原漏洞行为)") + }) +} diff --git a/plugin/dapp/js/types/js.go b/plugin/dapp/js/types/js.go index 2b5a4e2275..9e197e1754 100644 --- a/plugin/dapp/js/types/js.go +++ b/plugin/dapp/js/types/js.go @@ -35,6 +35,14 @@ var ( //JsX 插件名字 var JsX = "jsvm" +// dapp 分叉 +const ( + // ForkJSFixAddrNormalize 修复 exec_transfer 缺少地址归一化的问题: + // eth 地址大小写变体可绕过 account 层 from==to 自我转账检查(存储 key 已归一化为小写), + // 分叉后在调用 account 层前先将 from/to 归一化 + ForkJSFixAddrNormalize = "ForkJSFixAddrNormalize" +) + //错误常量 var ( ErrDupName = errors.New("ErrDupName") @@ -63,6 +71,7 @@ func init() { //InitFork ... func InitFork(cfg *types.Chain33Config) { cfg.RegisterDappFork(JsX, "Enable", 0) + cfg.RegisterDappFork(JsX, ForkJSFixAddrNormalize, 0) } //InitExecutor ... diff --git a/plugin/dapp/wasm/executor/callback.go b/plugin/dapp/wasm/executor/callback.go index d5e2cb5a93..77026222ec 100644 --- a/plugin/dapp/wasm/executor/callback.go +++ b/plugin/dapp/wasm/executor/callback.go @@ -4,6 +4,7 @@ import ( "github.com/33cn/chain33/common" "github.com/33cn/chain33/common/address" "github.com/33cn/chain33/types" + dappcommon "github.com/33cn/plugin/plugin/dapp/common" types2 "github.com/33cn/plugin/plugin/dapp/wasm/types" ) @@ -112,6 +113,13 @@ func execActive(addr string, amount int64) error { } func execTransfer(from, to string, amount int64) error { + cfg := wasmCB.GetAPI().GetConfig() + // 分叉后对 from/to 做 eth 地址归一化(与存储 key 格式一致), 防止大小写变体 + // 绕过 account 层 from==to 自我转账检查造成造币 + if cfg.IsDappFork(wasmCB.GetHeight(), types2.WasmX, types2.ForkWasmFixAddrNormalize) { + from = dappcommon.FmtEthAddressWithFork(from, cfg, wasmCB.GetHeight()) + to = dappcommon.FmtEthAddressWithFork(to, cfg, wasmCB.GetHeight()) + } receipt, err := wasmCB.GetCoinsAccount().ExecTransfer(from, to, wasmCB.execAddr, amount) if err != nil { return err @@ -122,6 +130,12 @@ func execTransfer(from, to string, amount int64) error { } func execTransferFrozen(from, to string, amount int64) error { + cfg := wasmCB.GetAPI().GetConfig() + // 同 execTransfer, 分叉后对 from/to 做 eth 地址归一化 + if cfg.IsDappFork(wasmCB.GetHeight(), types2.WasmX, types2.ForkWasmFixAddrNormalize) { + from = dappcommon.FmtEthAddressWithFork(from, cfg, wasmCB.GetHeight()) + to = dappcommon.FmtEthAddressWithFork(to, cfg, wasmCB.GetHeight()) + } receipt, err := wasmCB.GetCoinsAccount().ExecTransferFrozen(from, to, wasmCB.execAddr, amount) if err != nil { return err diff --git a/plugin/dapp/wasm/executor/vuln_fix_test.go b/plugin/dapp/wasm/executor/vuln_fix_test.go new file mode 100644 index 0000000000..174a889428 --- /dev/null +++ b/plugin/dapp/wasm/executor/vuln_fix_test.go @@ -0,0 +1,118 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package executor + +// 安全漏洞修复回归测试(executor 级): +// wasm dapp 向合约暴露的宿主函数 execTransfer/execTransferFrozen +// (resolver.go -> callback.go)此前对合约传入的 from/to 字符串不做任何归一化, +// 直接调用 acc.ExecTransfer/ExecTransferFrozen。account 层用原始字符串比较 +// from==to 做自我转账检查, 而存储 key 经 address.FormatAddrKey 归一化为小写, +// 导致同一 eth 地址的大小写变体可绕过自我转账检查, 同 key 覆盖后余额不减反增, +// 凭空造币。 +// 修复: 分叉 ForkWasmFixAddrNormalize 开启后, callback 层先用 +// common.FmtEthAddressWithFork 将 from/to 归一化, 使大小写变体收敛为同一字符串, +// account 层 from==to 检查必然命中, 返回 ErrSendSameToRecv。 + +import ( + "strings" + "testing" + + "github.com/33cn/chain33/account" + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + types2 "github.com/33cn/plugin/plugin/dapp/wasm/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// 测试用 eth 地址: lower 与 mixed 为同一地址的两种大小写形式 +const ( + vulnFixWasmLowerAddr = "0x1a9a5a0bbe37a15e0cf2bd00b65a4a5ce07a3d01" + vulnFixWasmMixedAddr = "0x1A9A5a0bbe37a15E0cf2bd00b65a4a5ce07a3d01" + vulnFixWasmOtherAddr = "0x2b8c1e6f4d3a2b1c0f9e8d7c6b5a493827160504" +) + +// initVulnFixWasmCB 构造回调测试环境, forkHeight<=0 时分叉取默认注册高度(0, 始终生效) +func initVulnFixWasmCB(t *testing.T, forkHeight int64) (*Wasm, *account.DB, string) { + testCfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1)) + if forkHeight > 0 { + testCfg.SetDappFork(types2.WasmX, types2.ForkWasmFixAddrNormalize, forkHeight) + } + dir, ldb, kvdb := util.CreateTestDB() + t.Cleanup(func() { util.CloseTestDB(dir, ldb) }) + + execAddr := address.ExecAddress(testCfg.ExecName(types2.WasmX)) + acc, err := account.NewAccountDB(testCfg, "coins", "bty", kvdb) + require.Nil(t, err) + //为测试地址准备主账户余额, 供 transferToExec 充值执行器账户 + acc.SaveAccount(&types.Account{Balance: 100 * types.DefaultCoinPrecision, Addr: vulnFixWasmLowerAddr}) + acc.SaveAccount(&types.Account{Balance: 100 * types.DefaultCoinPrecision, Addr: vulnFixWasmOtherAddr}) + + wasm := newWasm().(*Wasm) + wasm.SetCoinsAccount(acc) + wasm.SetStateDB(kvdb) + wasm.SetLocalDB(kvdb) + wasm.execAddr = execAddr + api := mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(testCfg) + api.On("GetRandNum", mock.Anything).Return([]byte("hello"), nil) + wasm.SetAPI(&api) + wasmCB = wasm + t.Cleanup(func() { wasmCB = nil }) + return wasm, acc, execAddr +} + +func TestVulnFixWasmExecTransferAddrNormalize(t *testing.T) { + amount := types.DefaultCoinPrecision + + //分叉后: 同一 eth 地址的大小写变体自我转账被归一化后命中 from==to 检查, 报错且余额不变 + t.Run("post-fork reject mixed-case self transfer", func(t *testing.T) { + _, acc, execAddr := initVulnFixWasmCB(t, 0) + require.Nil(t, transferToExec(vulnFixWasmLowerAddr, execAddr, amount)) + + err := execTransfer(vulnFixWasmLowerAddr, vulnFixWasmMixedAddr, amount) + require.Equal(t, types.ErrSendSameToRecv, err, "归一化后应命中 account 层自我转账检查") + require.Equal(t, amount, acc.LoadExecAccount(vulnFixWasmLowerAddr, execAddr).Balance, "余额不变, 未造币") + require.Equal(t, amount, acc.LoadExecAccount(vulnFixWasmMixedAddr, execAddr).Balance, "归一化后同一账户, 余额不变") + }) + + //分叉后: execTransferFrozen 同样归一化, 大小写变体自我转账被拒绝 + t.Run("post-fork reject mixed-case self transferFrozen", func(t *testing.T) { + _, acc, execAddr := initVulnFixWasmCB(t, 0) + require.Nil(t, transferToExec(vulnFixWasmLowerAddr, execAddr, amount)) + require.Nil(t, execFrozen(vulnFixWasmLowerAddr, amount)) + + err := execTransferFrozen(vulnFixWasmLowerAddr, vulnFixWasmMixedAddr, amount) + require.Equal(t, types.ErrSendSameToRecv, err, "归一化后应命中 account 层自我转账检查") + execAcc := acc.LoadExecAccount(vulnFixWasmLowerAddr, execAddr) + require.Equal(t, int64(0), execAcc.Balance, "余额不变") + require.Equal(t, amount, execAcc.Frozen, "冻结余额不变, 未造币") + }) + + //分叉后: 正常的不同地址转账不受影响, 混合大小写的 to 归一化后入账到同一账户 + t.Run("post-fork normal transfer unaffected", func(t *testing.T) { + _, acc, execAddr := initVulnFixWasmCB(t, 0) + require.Nil(t, transferToExec(vulnFixWasmLowerAddr, execAddr, amount)) + + otherMixed := "0x2B8c1e6f4d3a2b1c0f9e8d7c6b5a493827160504" + require.Nil(t, execTransfer(vulnFixWasmLowerAddr, otherMixed, amount/2)) + require.Equal(t, amount/2, acc.LoadExecAccount(vulnFixWasmLowerAddr, execAddr).Balance) + require.Equal(t, amount/2, acc.LoadExecAccount(vulnFixWasmOtherAddr, execAddr).Balance) + }) + + //分叉前: 保持原行为(不做归一化), 大小写变体自我转账仍可执行, 保证链上共识兼容 + t.Run("pre-fork behavior unchanged", func(t *testing.T) { + wasm, acc, execAddr := initVulnFixWasmCB(t, 1000000) + require.False(t, wasm.GetAPI().GetConfig().IsDappFork(wasm.GetHeight(), types2.WasmX, types2.ForkWasmFixAddrNormalize)) + require.Nil(t, transferToExec(vulnFixWasmLowerAddr, execAddr, amount)) + + require.Nil(t, execTransfer(vulnFixWasmLowerAddr, vulnFixWasmMixedAddr, amount), + "分叉前保持原行为, 不做归一化") + require.Equal(t, 2*amount, acc.LoadExecAccount(vulnFixWasmLowerAddr, execAddr).Balance, + "分叉前 from==to 字符串检查被绕过, 同 key 覆盖后余额翻倍(原漏洞行为)") + }) +} diff --git a/plugin/dapp/wasm/types/wasm.go b/plugin/dapp/wasm/types/wasm.go index 716f482e17..ed12ac8946 100644 --- a/plugin/dapp/wasm/types/wasm.go +++ b/plugin/dapp/wasm/types/wasm.go @@ -14,6 +14,11 @@ const ( NameRegExp = "^[a-z0-9]+$" //TODO: max size to define MaxCodeSize = 1 << 20 + + // ForkWasmFixAddrNormalize 修复 execTransfer/execTransferFrozen 宿主函数缺少地址归一化的问题: + // eth 地址大小写变体可绕过 account 层 from==to 自我转账检查(存储 key 已归一化为小写), + // 分叉后在调用 account 层前先将 from/to 归一化 + ForkWasmFixAddrNormalize = "ForkWasmFixAddrNormalize" ) // action for executor @@ -42,6 +47,7 @@ func init() { func InitFork(cfg *types.Chain33Config) { cfg.RegisterDappFork(WasmX, "Enable", 0) + cfg.RegisterDappFork(WasmX, ForkWasmFixAddrNormalize, 0) } func InitExecutor(cfg *types.Chain33Config) {