diff --git a/app/receipt_test.go b/app/receipt_test.go index d1ce00052e..a5214b0aa3 100644 --- a/app/receipt_test.go +++ b/app/receipt_test.go @@ -110,10 +110,12 @@ func TestEvmEventsForCw20(t *testing.T) { tx = txBuilder.GetTx() txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) require.Nil(t, err) + sum = sha256.Sum256(txbz) res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum) require.Equal(t, uint32(0), res.Code) receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash()) require.Nil(t, err) + fmt.Println(receipt.Logs) require.Equal(t, 1, len(receipt.Logs)) require.NotEmpty(t, receipt.LogsBloom) require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) @@ -225,6 +227,7 @@ func TestEvmEventsForCw721(t *testing.T) { tx = txBuilder.GetTx() txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) require.Nil(t, err) + sum = sha256.Sum256(txbz) res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum) require.Equal(t, uint32(0), res.Code) receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash()) diff --git a/contracts/test/CW20toERC20PointerTest.js b/contracts/test/CW20toERC20PointerTest.js index 8c695260e9..317e321891 100644 --- a/contracts/test/CW20toERC20PointerTest.js +++ b/contracts/test/CW20toERC20PointerTest.js @@ -164,6 +164,39 @@ describe("CW20 to ERC20 Pointer", function () { const balanceAfter = respAfter.data.balance; expect(balanceAfter).to.equal((parseInt(balanceBefore) - 100).toString()); }); + + it("should transfer if called through wasmd precompile", async function() { + const WasmPrecompileContract = '0x0000000000000000000000000000000000001002'; + const contractABIPath = '../../precompiles/wasmd/abi.json'; + const contractABI = require(contractABIPath); + wasmd = new ethers.Contract(WasmPrecompileContract, contractABI, accounts[0].signer); + + const respBefore = await queryWasm(pointer, "balance", {address: accounts[1].seiAddress}); + const balanceBefore = respBefore.data.balance; + + const encoder = new TextEncoder(); + + const transferMsg = { transfer: { recipient: accounts[1].seiAddress, amount: "100" } }; + const transferStr = JSON.stringify(transferMsg); + const transferBz = encoder.encode(transferStr); + + const coins = []; + const coinsStr = JSON.stringify(coins); + const coinsBz = encoder.encode(coinsStr); + + const blockNumber = await ethers.provider.getBlockNumber(); + const response = await wasmd.execute(pointer, transferBz, coinsBz); + const receipt = await response.wait(); + expect(receipt.status).to.equal(1); + const filter = { + fromBlock: blockNumber, + toBlock: 'latest', + address: tokenAddr, + topics: [ethers.id("Transfer(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + }); }); }); } diff --git a/go.mod b/go.mod index 589a271881..57a11e358b 100644 --- a/go.mod +++ b/go.mod @@ -346,7 +346,7 @@ require ( replace ( github.com/CosmWasm/wasmd => github.com/sei-protocol/sei-wasmd v0.2.3 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.29 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.30 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.2 github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-22 diff --git a/go.sum b/go.sum index 6d308e3a01..47e6c75672 100644 --- a/go.sum +++ b/go.sum @@ -1347,8 +1347,8 @@ github.com/sei-protocol/go-ethereum v1.13.5-sei-22 h1:t/m1qXER+DEMrcpqgoYmUxifkA github.com/sei-protocol/go-ethereum v1.13.5-sei-22/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= github.com/sei-protocol/goutils v0.0.2 h1:Bfa7Sv+4CVLNM20QcpvGb81B8C5HkQC/kW1CQpIbXDA= github.com/sei-protocol/goutils v0.0.2/go.mod h1:iYE2DuJfEnM+APPehr2gOUXfuLuPsVxorcDO+Tzq9q8= -github.com/sei-protocol/sei-cosmos v0.3.29 h1:QQUTsqtLq9kpXCDZ+KHzEJKzdkgT7YlmXcZ3ygGFWQw= -github.com/sei-protocol/sei-cosmos v0.3.29/go.mod h1:og/KbejR/zSQ8otapODEDU9zYNhFSUDbq9+tgeYePyU= +github.com/sei-protocol/sei-cosmos v0.3.30 h1:7yt7NV2pY5mUu45VShzLKKOJP7/Ns4HAtTcK85R8utk= +github.com/sei-protocol/sei-cosmos v0.3.30/go.mod h1:og/KbejR/zSQ8otapODEDU9zYNhFSUDbq9+tgeYePyU= github.com/sei-protocol/sei-db v0.0.40 h1:s6B3u9u0r2Ypd67P8Lrz2IR/QU/FXwtS2X/fnYEix2g= github.com/sei-protocol/sei-db v0.0.40/go.mod h1:F/ZKZA8HJPcUzSZPA8yt6pfwlGriJ4RDR4eHKSGLStI= github.com/sei-protocol/sei-iavl v0.1.9 h1:y4mVYftxLNRs6533zl7N0/Ch+CzRQc04JDfHolIxgBE= diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 279132e8db..daba4447c9 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -27,6 +27,8 @@ const ( const WasmdAddress = "0x0000000000000000000000000000000000001002" +var Address = common.HexToAddress(WasmdAddress) + // Embed abi json file to the executable binary. Needed when importing as dependency. // //go:embed abi.json @@ -51,15 +53,19 @@ type ExecuteMsg struct { Coins []byte `json:"coins"` } +func GetABI() abi.ABI { + return pcommon.MustGetABI(f, "abi.json") +} + func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { - newAbi := pcommon.MustGetABI(f, "abi.json") + newAbi := GetABI() executor := &PrecompileExecutor{ wasmdKeeper: wasmdKeeper, wasmdViewKeeper: wasmdViewKeeper, evmKeeper: evmKeeper, bankKeeper: bankKeeper, - address: common.HexToAddress(WasmdAddress), + address: Address, } for name, m := range newAbi.Methods { @@ -74,7 +80,7 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, executor.QueryID = m.ID } } - return pcommon.NewDynamicGasPrecompile(newAbi, executor, common.HexToAddress(WasmdAddress), "wasmd"), nil + return pcommon.NewDynamicGasPrecompile(newAbi, executor, Address, "wasmd"), nil } func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index b653ed14d7..1e6670c51b 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -154,6 +154,7 @@ func TestExecute(t *testing.T) { testApp.BankKeeper.SendCoins(ctx, mockAddr, testApp.EvmKeeper.GetSeiAddressOrDefault(ctx, common.HexToAddress(wasmd.WasmdAddress)), amts) // circular interop statedb.WithCtx(statedb.Ctx().WithIsEVM(false)) + testApp.EvmKeeper.SetCode(statedb.Ctx(), mockEVMAddr, []byte{1, 2, 3}) res, _, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.GetExecutor().(*wasmd.PrecompileExecutor).ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil, false) require.Equal(t, "sei does not support CW->EVM->CW call pattern", string(res)) require.Equal(t, vm.ErrExecutionReverted, err) diff --git a/x/evm/keeper/evm.go b/x/evm/keeper/evm.go index 97695dc14f..1479121400 100644 --- a/x/evm/keeper/evm.go +++ b/x/evm/keeper/evm.go @@ -73,7 +73,7 @@ func (k *Keeper) HandleInternalEVMDelegateCall(ctx sdk.Context, req *types.MsgIn } func (k *Keeper) CallEVM(ctx sdk.Context, from common.Address, to *common.Address, val *sdk.Int, data []byte) (retdata []byte, reterr error) { - if ctx.IsEVM() { + if ctx.IsEVM() && !ctx.EVMEntryViaWasmdPrecompile() { return nil, errors.New("sei does not support EVM->CW->EVM call pattern") } if to == nil && len(data) > params.MaxInitCodeSize { @@ -87,7 +87,7 @@ func (k *Keeper) CallEVM(ctx sdk.Context, from common.Address, to *common.Addres value = val.BigInt() } // This call was not part of an existing StateTransition, so it should trigger one - executionCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) + executionCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)).WithEVMEntryViaWasmdPrecompile(false) stateDB := state.NewDBImpl(executionCtx, k, false) gp := k.GetGasPool() evmMsg := &core.Message{ @@ -118,6 +118,23 @@ func (k *Keeper) CallEVM(ctx sdk.Context, from common.Address, to *common.Addres if res.Err != nil { vmErr = res.Err.Error() } + existingReceipt, err := k.GetTransientReceipt(ctx, ctx.TxSum()) + if err == nil { + for _, l := range existingReceipt.Logs { + stateDB.AddLog(ðtypes.Log{ + Address: common.HexToAddress(l.Address), + Topics: utils.Map(l.Topics, common.HexToHash), + Data: l.Data, + }) + } + if existingReceipt.VmError != "" { + vmErr = fmt.Sprintf("%s\n%s\n", existingReceipt.VmError, vmErr) + } + } + existingDeferredInfo, found := k.GetEVMTxDeferredInfo(ctx) + if found { + surplus = surplus.Add(existingDeferredInfo.Surplus) + } receipt, err := k.WriteReceipt(ctx, stateDB, evmMsg, ethtypes.LegacyTxType, ctx.TxSum(), res.UsedGas, vmErr) if err != nil { return nil, err diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 3b08a46405..c3e2c17778 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -21,6 +21,8 @@ import ( "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/precompiles/wasmd" + "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" "github.com/sei-protocol/sei-chain/x/evm/state" @@ -45,6 +47,11 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT return &types.MsgEVMTransactionResponse{}, nil } ctx := sdk.UnwrapSDKContext(goCtx) + tx, _ := msg.AsTransaction() + isWasmdPrecompileCall := tx.To() != nil && (tx.To().Cmp(wasmd.Address) == 0) + if isWasmdPrecompileCall { + ctx = ctx.WithEVMEntryViaWasmdPrecompile(true) + } // EVM has a special case here, mainly because for an EVM transaction the gas limit is set on EVM payload level, not on top-level GasWanted field // as normal transactions (because existing eth client can't). As a result EVM has its own dedicated ante handler chain. The full sequence is: @@ -56,7 +63,6 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) stateDB := state.NewDBImpl(ctx, &server, false) - tx, _ := msg.AsTransaction() emsg := server.GetEVMMessage(ctx, tx, msg.Derived.SenderEVMAddr) gp := server.GetGasPool() @@ -81,6 +87,27 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT ) return } + extraSurplus := sdk.ZeroInt() + if isWasmdPrecompileCall { + syntheticReceipt, err := server.GetTransientReceipt(ctx, ctx.TxSum()) + if err == nil { + for _, l := range syntheticReceipt.Logs { + stateDB.AddLog(ðtypes.Log{ + Address: common.HexToAddress(l.Address), + Topics: utils.Map(l.Topics, common.HexToHash), + Data: l.Data, + }) + } + if syntheticReceipt.VmError != "" { + serverRes.VmError = fmt.Sprintf("%s\n%s\n", serverRes.VmError, syntheticReceipt.VmError) + } + server.DeleteTransientReceipt(ctx, ctx.TxSum()) + } + syntheticDeferredInfo, found := server.GetEVMTxDeferredInfo(ctx) + if found { + extraSurplus = extraSurplus.Add(syntheticDeferredInfo.Surplus) + } + } receipt, rerr := server.WriteReceipt(ctx, stateDB, emsg, uint32(tx.Type()), tx.Hash(), serverRes.GasUsed, serverRes.VmError) if rerr != nil { err = rerr @@ -117,6 +144,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT ) return } + surplus = surplus.Add(extraSurplus) bloom := ethtypes.Bloom{} bloom.SetBytes(receipt.LogsBloom) server.AppendToEvmTxDeferredInfo(ctx, bloom, tx.Hash(), surplus) diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index fa2c79e2b6..1f01342097 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -41,6 +41,11 @@ func (k *Keeper) GetTransientReceipt(ctx sdk.Context, txHash common.Hash) (*type return r, nil } +func (k *Keeper) DeleteTransientReceipt(ctx sdk.Context, txHash common.Hash) { + store := ctx.TransientStore(k.transientStoreKey) + store.Delete(types.ReceiptKey(txHash)) +} + // GetReceipt returns a data structure that stores EVM specific transaction metadata. // Many EVM applications (e.g. MetaMask) relies on being on able to query receipt // by EVM transaction hash (not Sei transaction hash) to function properly.