Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow CW->ERC pointers to be called through wasmd precompile #1785

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
if idx == txIndex {
return tx, *blockContext, statedb, emptyRelease, nil
}
statedb.WithCtx(statedb.Ctx().WithEVMEntryViaWasmdPrecompile(wasmd.IsWasmdCall(tx.To())))

Check warning on line 341 in evmrpc/simulate.go

View check run for this annotation

Codecov / codecov/patch

evmrpc/simulate.go#L341

Added line #L341 was not covered by tests
// 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) 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 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 @@
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 @@
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 @@
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)
return
}

func IsWasmdCall(to *common.Address) bool {
return to != nil && (to.Cmp(Address) == 0)

Check warning on line 463 in precompiles/wasmd/wasmd.go

View check run for this annotation

Codecov / codecov/patch

precompiles/wasmd/wasmd.go#L462-L463

Added lines #L462 - L463 were not covered by tests
}
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) 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 @@
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 @@
if res.Err != nil {
vmErr = res.Err.Error()
}
existingReceipt, err := k.GetTransientReceipt(ctx, ctx.TxSum())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this logic related to the wasmd precompile stuff?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's to merge with the temp receipts created in the CW->EVM hop

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)
}

Check warning on line 132 in x/evm/keeper/evm.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/evm.go#L131-L132

Added lines #L131 - L132 were not covered by tests
}
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 @@
"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 @@
return &types.MsgEVMTransactionResponse{}, nil
}
ctx := sdk.UnwrapSDKContext(goCtx)
tx, _ := msg.AsTransaction()
isWasmdPrecompileCall := wasmd.IsWasmdCall(tx.To())
if isWasmdPrecompileCall {
ctx = ctx.WithEVMEntryViaWasmdPrecompile(true)
}

Check warning on line 54 in x/evm/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/msg_server.go#L53-L54

Added lines #L53 - L54 were not covered by tests
// 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 @@
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 @@
)
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())

Check warning on line 105 in x/evm/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/msg_server.go#L93-L105

Added lines #L93 - L105 were not covered by tests
}
syntheticDeferredInfo, found := server.GetEVMTxDeferredInfo(ctx)
if found {
extraSurplus = extraSurplus.Add(syntheticDeferredInfo.Surplus)
}

Check warning on line 110 in x/evm/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/msg_server.go#L107-L110

Added lines #L107 - L110 were not covered by tests
}
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 @@
)
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 @@
return r, nil
}

func (k *Keeper) DeleteTransientReceipt(ctx sdk.Context, txHash common.Hash) {
store := ctx.TransientStore(k.transientStoreKey)
store.Delete(types.ReceiptKey(txHash))

Check warning on line 46 in x/evm/keeper/receipt.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/receipt.go#L44-L46

Added lines #L44 - L46 were not covered by tests
}

// 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
Loading