Skip to content

Commit

Permalink
add create2 interface
Browse files Browse the repository at this point in the history
  • Loading branch information
beer-1 committed Apr 1, 2024
1 parent 3403c4d commit 199a88a
Show file tree
Hide file tree
Showing 5 changed files with 702 additions and 55 deletions.
30 changes: 27 additions & 3 deletions proto/minievm/evm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ option go_package = "github.com/initia-labs/minievm/x/evm/types";
service Msg {
option (cosmos.msg.v1.service) = true;

// Create defines a method submitting Ethereum contract byte code.
// Create defines a method calling create of EVM.
rpc Create(MsgCreate) returns (MsgCreateResponse);
// Create2 defines a method calling create2 of EVM.
rpc Create2(MsgCreate2) returns (MsgCreate2Response);
// Call defines a method submitting Ethereum transactions.
rpc Call(MsgCall) returns (MsgCallResponse);
// UpdateParams defines an operation for updating the x/evm module
// parameters.
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
}

// MsgCreate is a message to create a new contract using code as deployment
// code.
// MsgCreate is a message to create a contract with the CREATE opcode.
message MsgCreate {
option (cosmos.msg.v1.signer) = "sender";
option (amino.name) = "evm/MsgCreate";
Expand All @@ -43,6 +44,29 @@ message MsgCreateResponse {
string contract_addr = 2;
}

// MsgCreate2 is a message to create a contract with the CREATE2 opcode.
message MsgCreate2 {
option (cosmos.msg.v1.signer) = "sender";
option (amino.name) = "evm/MsgCreate2";

// Sender is the that actor that signed the messages
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// Code is hex encoded raw contract bytes code.
string code = 2;

// Salt is a random value to distinguish contract creation.
uint64 salt = 3;
}

// MsgCreate2Response defines the Msg/Create2 response type.
message MsgCreate2Response {
string result = 1;

// hex encoded address
string contract_addr = 2;
}

// MsgCall is a message to call an Ethereum contract.
message MsgCall {
option (cosmos.msg.v1.signer) = "sender";
Expand Down
38 changes: 29 additions & 9 deletions x/evm/keeper/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,43 @@ func (k Keeper) EVMCallWithTracer(ctx context.Context, caller common.Address, co

// EVMCreate creates a new contract with the given code.
func (k Keeper) EVMCreate(ctx context.Context, caller common.Address, codeBz []byte) ([]byte, common.Address, error) {
return k.EVMCreateWithTracer(ctx, caller, codeBz, nil)
return k.EVMCreateWithTracer(ctx, caller, codeBz, nil, nil)
}

// EVMCreate creates a new contract with the given code.
func (k Keeper) EVMCreate2(ctx context.Context, caller common.Address, codeBz []byte, salt uint64) ([]byte, common.Address, error) {
return k.EVMCreateWithTracer(ctx, caller, codeBz, &salt, nil)
}

// EVMCreateWithTracer creates a new contract with the given code and tracer.
func (k Keeper) EVMCreateWithTracer(ctx context.Context, caller common.Address, codeBz []byte, tracer *tracing.Hooks) ([]byte, common.Address, error) {
// if salt is nil, it will create a contract with the CREATE opcode.
// if salt is not nil, it will create a contract with the CREATE2 opcode.
func (k Keeper) EVMCreateWithTracer(ctx context.Context, caller common.Address, codeBz []byte, salt *uint64, tracer *tracing.Hooks) (retBz []byte, contractAddr common.Address, err error) {
ctx, evm, err := k.createEVM(ctx, caller, tracer)
if err != nil {
return nil, common.Address{}, err
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
gasBalance := k.computeGasLimit(sdkCtx)
retBz, contractAddr, gasRemaining, err := evm.Create(
vm.AccountRef(caller),
codeBz,
gasBalance,
uint256.NewInt(0),
)

var gasRemaining uint64
if salt == nil {
retBz, contractAddr, gasRemaining, err = evm.Create(
vm.AccountRef(caller),
codeBz,
gasBalance,
uint256.NewInt(0),
)
} else {
retBz, contractAddr, gasRemaining, err = evm.Create2(
vm.AccountRef(caller),
codeBz,
gasBalance,
uint256.NewInt(0),
uint256.NewInt(*salt),
)
}

// London enforced
gasUsed := types.CalGasUsed(gasBalance, gasRemaining, evm.StateDB.GetRefund())
Expand Down Expand Up @@ -339,7 +358,8 @@ func (k Keeper) EVMCreateWithTracer(ctx context.Context, caller common.Address,
return retBz, contractAddr, nil
}

// nextContractAddress returns the next contract address which will be created by the given caller.
// nextContractAddress returns the next contract address which will be created by the given caller
// in CREATE opcode.
func (k Keeper) nextContractAddress(ctx context.Context, caller common.Address) (common.Address, error) {
stateDB, err := k.newStateDB(ctx)
if err != nil {
Expand Down
54 changes: 54 additions & 0 deletions x/evm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,60 @@ func (ms *msgServerImpl) Create(ctx context.Context, msg *types.MsgCreate) (*typ
}, nil
}

// Create2 implements types.MsgServer.
func (ms *msgServerImpl) Create2(ctx context.Context, msg *types.MsgCreate2) (*types.MsgCreate2Response, error) {
sender, err := ms.ac.StringToBytes(msg.Sender)
if err != nil {
return nil, err
}

// argument validation
caller, err := ms.convertToEVMAddress(ctx, sender)
if err != nil {
return nil, err
}
if len(msg.Code) == 0 {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty code bytes")
}
codeBz, err := hexutil.Decode(msg.Code)
if err != nil {
return nil, types.ErrInvalidHexString.Wrap(err.Error())
}

// check the sender is allowed publisher
params, err := ms.Params.Get(ctx)
if err != nil {
return nil, err
}

// assert deploy authorization
if len(params.AllowedPublishers) != 0 {
allowed := false
for _, publisher := range params.AllowedPublishers {
if msg.Sender == publisher {
allowed = true

break
}
}

if !allowed {
return nil, sdkerrors.ErrUnauthorized.Wrapf("`%s` is not allowed to deploy a contract", msg.Sender)
}
}

// deploy a contract
retBz, contractAddr, err := ms.EVMCreate2(ctx, caller, codeBz, msg.Salt)
if err != nil {
return nil, types.ErrEVMCallFailed.Wrap(err.Error())
}

return &types.MsgCreate2Response{
Result: hexutil.Encode(retBz),
ContractAddr: contractAddr.Hex(),
}, nil
}

// Call implements types.MsgServer.
func (ms *msgServerImpl) Call(ctx context.Context, msg *types.MsgCall) (*types.MsgCallResponse, error) {
sender, err := ms.ac.StringToBytes(msg.Sender)
Expand Down
26 changes: 26 additions & 0 deletions x/evm/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
"github.com/initia-labs/minievm/x/evm/contracts/counter"
"github.com/initia-labs/minievm/x/evm/keeper"
Expand All @@ -15,6 +16,7 @@ import (
func Test_MsgServer_Create(t *testing.T) {
ctx, input := createDefaultTestInput(t)
_, _, addr := keyPubAddr()
evmAddr := common.BytesToAddress(addr.Bytes())

msgServer := keeper.NewMsgServerImpl(&input.EVMKeeper)
res, err := msgServer.Create(ctx, &types.MsgCreate{
Expand All @@ -25,6 +27,10 @@ func Test_MsgServer_Create(t *testing.T) {
require.NotEmpty(t, res.Result)
require.True(t, common.IsHexAddress(res.ContractAddr))

// check generated contract address
expectedContractAddr := crypto.CreateAddress(evmAddr, 0)
require.Equal(t, expectedContractAddr, common.HexToAddress(res.ContractAddr))

// update params to set allowed publishers
params := types.DefaultParams()
params.AllowedPublishers = []string{addr.String()}
Expand All @@ -49,6 +55,26 @@ func Test_MsgServer_Create(t *testing.T) {
require.Error(t, err)
}

func Test_MsgServer_Create2(t *testing.T) {
ctx, input := createDefaultTestInput(t)
_, _, addr := keyPubAddr()
evmAddr := common.BytesToAddress(addr.Bytes())

msgServer := keeper.NewMsgServerImpl(&input.EVMKeeper)
res, err := msgServer.Create2(ctx, &types.MsgCreate2{
Sender: addr.String(),
Code: counter.CounterBin,
Salt: 1,
})
require.NoError(t, err)
require.NotEmpty(t, res.Result)

// check generated contract address
salt := uint256.NewInt(1)
expectedContractAddr := crypto.CreateAddress2(evmAddr, salt.Bytes32(), crypto.Keccak256Hash(hexutil.MustDecode(counter.CounterBin)).Bytes())
require.Equal(t, expectedContractAddr, common.HexToAddress(res.ContractAddr))
}

func Test_MsgServer_Call(t *testing.T) {
ctx, input := createDefaultTestInput(t)
_, _, addr := keyPubAddr()
Expand Down
Loading

0 comments on commit 199a88a

Please sign in to comment.