Skip to content

Commit

Permalink
Allow CW->ERC pointers to be called through wasmd precompile
Browse files Browse the repository at this point in the history
  • Loading branch information
codchen committed Jul 29, 2024
1 parent e439d52 commit ab68883
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 6 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
33 changes: 33 additions & 0 deletions contracts/test/CW20toERC20PointerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
}
Expand Down
29 changes: 25 additions & 4 deletions precompiles/wasmd/wasmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"

ethtypes "github.com/ethereum/go-ethereum/core/types"
pcommon "github.com/sei-protocol/sei-chain/precompiles/common"
"github.com/sei-protocol/sei-chain/x/evm/state"
"github.com/sei-protocol/sei-chain/x/evm/types"
Expand All @@ -27,6 +28,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 +54,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,11 +81,25 @@ 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) {
func (p PrecompileExecutor) validateExecution(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address) bool {
if method.Name != QueryMethod && !ctx.IsEVM() {
// okay if this is called by an EOA directly
if caller.Cmp(callingContract) == 0 {
codeHash := p.evmKeeper.GetCodeHash(ctx, caller)
if codeHash.Cmp(common.Hash{}) == 0 || codeHash.Cmp(ethtypes.EmptyCodeHash) == 0 {
return true
}

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

View check run for this annotation

Codecov / codecov/patch

precompiles/wasmd/wasmd.go#L93-L94

Added lines #L93 - L94 were not covered by tests
}
return false
}
return true
}

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) {
if !p.validateExecution(ctx, method, caller, callingContract) {
return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern")
}
switch method.Name {
Expand Down
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
2 changes: 1 addition & 1 deletion x/evm/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)).WithIsEVM(true)
executionCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx))
stateDB := state.NewDBImpl(executionCtx, k, false)
gp := k.GetGasPool()
evmMsg := &core.Message{
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 := tx.To() != nil && (tx.To().Cmp(wasmd.Address) == 0)
if isWasmdPrecompileCall {
ctx = ctx.WithIsEVM(false) // do not count the initial wasm precompile call -> CW as an interop hop
}

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 @@ 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 @@ -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(&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 104 in x/evm/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/msg_server.go#L92-L104

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

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

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/msg_server.go#L106-L109

Added lines #L106 - L109 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 @@ -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)
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))

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

0 comments on commit ab68883

Please sign in to comment.