Skip to content

Commit

Permalink
Allow CW->ERC pointers to be called through wasmd precompile (sei-pro…
Browse files Browse the repository at this point in the history
  • Loading branch information
codchen authored Aug 13, 2024
1 parent c2d7925 commit fc36b39
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 10 deletions.
3 changes: 3 additions & 0 deletions app/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down
21 changes: 21 additions & 0 deletions contracts/test/CW20toERC20PointerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,27 @@ 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 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 response = await wasmd.execute(pointer, transferBz, coinsBz);
const receipt = await response.wait();
expect(receipt.status).to.equal(1);
});
});
});
}
Expand Down
18 changes: 14 additions & 4 deletions evmrpc/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/sei-protocol/sei-chain/precompiles/wasmd"
"github.com/sei-protocol/sei-chain/utils/helpers"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -34,6 +35,10 @@ import (
"github.com/tendermint/tendermint/rpc/coretypes"
)

type CtxIsWasmdPrecompileCallKeyType string

const CtxIsWasmdPrecompileCallKey CtxIsWasmdPrecompileCallKeyType = "CtxIsWasmdPrecompileCallKey"

type SimulationAPI struct {
backend *Backend
connectionType ConnectionType
Expand Down Expand Up @@ -66,6 +71,7 @@ func (s *SimulationAPI) CreateAccessList(ctx context.Context, args ethapi.Transa
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
ctx = context.WithValue(ctx, CtxIsWasmdPrecompileCallKey, wasmd.IsWasmdCall(args.To))
acl, gasUsed, vmerr, err := ethapi.AccessList(ctx, s.backend, bNrOrHash, args)
if err != nil {
return nil, err
Expand All @@ -84,6 +90,7 @@ func (s *SimulationAPI) EstimateGas(ctx context.Context, args ethapi.Transaction
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
ctx = context.WithValue(ctx, CtxIsWasmdPrecompileCallKey, wasmd.IsWasmdCall(args.To))
estimate, err := ethapi.DoEstimateGas(ctx, s.backend, args, bNrOrHash, overrides, s.backend.RPCGasCap())
return estimate, err
}
Expand All @@ -104,6 +111,7 @@ func (s *SimulationAPI) Call(ctx context.Context, args ethapi.TransactionArgs, b
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
blockNrOrHash = &latest
}
ctx = context.WithValue(ctx, CtxIsWasmdPrecompileCallKey, wasmd.IsWasmdCall(args.To))
callResult, err := ethapi.DoCall(ctx, s.backend, args, *blockNrOrHash, overrides, blockOverrides, s.backend.RPCEVMTimeout(), s.backend.RPCGasCap())
if err != nil {
return nil, err
Expand Down Expand Up @@ -176,11 +184,12 @@ func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHas
if err != nil {
return nil, nil, err
}
sdkCtx := b.ctxProvider(height)
isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool)
sdkCtx := b.ctxProvider(height).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall)
if err := CheckVersion(sdkCtx, b.keeper); err != nil {
return nil, nil, err
}
return state.NewDBImpl(b.ctxProvider(height), b.keeper, true), b.getHeader(big.NewInt(height)), nil
return state.NewDBImpl(sdkCtx, b.keeper, true), b.getHeader(big.NewInt(height)), nil
}

func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (tx *ethtypes.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, err error) {
Expand Down Expand Up @@ -291,7 +300,7 @@ func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block,
// get the parent block using block.parentHash
prevBlockHeight := block.Number().Int64() - 1
// Get statedb of parent block from the store
statedb := state.NewDBImpl(b.ctxProvider(prevBlockHeight), b.keeper, true)
statedb := state.NewDBImpl(b.ctxProvider(prevBlockHeight).WithIsEVM(true), b.keeper, true)
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, vm.BlockContext{}, statedb, emptyRelease, nil
}
Expand Down Expand Up @@ -329,6 +338,7 @@ func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block,
if idx == txIndex {
return tx, *blockContext, statedb, emptyRelease, nil
}
statedb.WithCtx(statedb.Ctx().WithEVMEntryViaWasmdPrecompile(wasmd.IsWasmdCall(tx.To())))
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(*blockContext, txContext, statedb, b.ChainConfig(), vm.Config{})
statedb.SetTxContext(tx.Hash(), idx)
Expand Down Expand Up @@ -371,7 +381,7 @@ func (b *Backend) StateAtBlock(ctx context.Context, block *ethtypes.Block, reexe
func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateDB, _ *ethtypes.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM {
txContext := core.NewEVMTxContext(msg)
if blockCtx == nil {
blockCtx, _ = b.keeper.GetVMBlockContext(b.ctxProvider(LatestCtxHeight), core.GasPool(b.RPCGasCap()))
blockCtx, _ = b.keeper.GetVMBlockContext(b.ctxProvider(LatestCtxHeight).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(wasmd.IsWasmdCall(msg.To)), core.GasPool(b.RPCGasCap()))
}
return vm.NewEVM(*blockCtx, txContext, stateDB, b.ChainConfig(), *vmConfig)
}
Expand Down
16 changes: 13 additions & 3 deletions precompiles/wasmd/wasmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -452,3 +458,7 @@ func (p PrecompileExecutor) query(ctx sdk.Context, method *abi.Method, args []in
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)
return
}

func IsWasmdCall(to *common.Address) bool {
return to != nil && (to.Cmp(Address) == 0)
}
1 change: 1 addition & 0 deletions precompiles/wasmd/wasmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 19 additions & 2 deletions x/evm/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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{
Expand Down Expand Up @@ -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(&ethtypes.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
Expand Down
30 changes: 29 additions & 1 deletion x/evm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 := wasmd.IsWasmdCall(tx.To())
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:

Expand All @@ -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()

Expand All @@ -82,6 +88,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(&ethtypes.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
Expand Down Expand Up @@ -118,6 +145,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)
Expand Down
5 changes: 5 additions & 0 deletions x/evm/keeper/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit fc36b39

Please sign in to comment.