add a basic FEVM integration test. (#9922)
* add a basic FEVM integration test. Exercises f4 addresses, placeholder transitions, Ethereum Account. * remove unused parameter from newEthTxFromFilecoinMessageLookup. * break when found in newEthTxFromFilecoinMessageLookup. * fixup test. * lint and gen. * move test to itests root package. Co-authored-by: Shrenuj Bansal <shrenuj.bansal@protocol.ai>
This commit is contained in:
parent
7073b33d9b
commit
e7aa7cb04f
@ -769,6 +769,11 @@ workflows:
|
|||||||
suite: itest-eth_account_abstraction
|
suite: itest-eth_account_abstraction
|
||||||
target: "./itests/eth_account_abstraction_test.go"
|
target: "./itests/eth_account_abstraction_test.go"
|
||||||
|
|
||||||
|
- test:
|
||||||
|
name: test-itest-eth_deploy
|
||||||
|
suite: itest-eth_deploy
|
||||||
|
target: "./itests/eth_deploy_test.go"
|
||||||
|
|
||||||
- test:
|
- test:
|
||||||
name: test-itest-eth_filter
|
name: test-itest-eth_filter
|
||||||
suite: itest-eth_filter
|
suite: itest-eth_filter
|
||||||
|
@ -581,7 +581,7 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
msg, err := txArgs.OriginalRlpMsg()
|
msg, err := txArgs.ToRlpUnsignedMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -796,7 +796,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("failed to convert to eth tx args: %w", err)
|
return xerrors.Errorf("failed to convert to eth tx args: %w", err)
|
||||||
}
|
}
|
||||||
msg, err := txArgs.OriginalRlpMsg()
|
msg, err := txArgs.ToRlpUnsignedMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/build"
|
"github.com/filecoin-project/lotus/build"
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/lib/sigs/delegated"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Eip1559TxType = 2
|
const Eip1559TxType = 2
|
||||||
@ -183,7 +184,50 @@ func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *EthTxArgs) OriginalRlpMsg() ([]byte, error) {
|
func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) {
|
||||||
|
msg, err := tx.ToRlpUnsignedMsg()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
hasher.Write(msg)
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) {
|
||||||
|
packed, err := tx.packTxFields()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := EncodeRLP(packed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append([]byte{0x02}, encoded...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) {
|
||||||
|
packed1, err := tx.packTxFields()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packed2, err := tx.packSigFields()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := EncodeRLP(append(packed1, packed2...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append([]byte{0x02}, encoded...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *EthTxArgs) packTxFields() ([]interface{}, error) {
|
||||||
chainId, err := formatInt(tx.ChainID)
|
chainId, err := formatInt(tx.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -225,12 +269,27 @@ func (tx *EthTxArgs) OriginalRlpMsg() ([]byte, error) {
|
|||||||
tx.Input,
|
tx.Input,
|
||||||
[]interface{}{}, // access list
|
[]interface{}{}, // access list
|
||||||
}
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
encoded, err := EncodeRLP(res)
|
func (tx *EthTxArgs) packSigFields() ([]interface{}, error) {
|
||||||
|
r, err := formatBigInt(tx.R)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return append([]byte{0x02}, encoded...), nil
|
|
||||||
|
s, err := formatBigInt(tx.S)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := formatBigInt(tx.V)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []interface{}{v, r, s}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) {
|
func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) {
|
||||||
@ -255,7 +314,7 @@ func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tx *EthTxArgs) Sender() (address.Address, error) {
|
func (tx *EthTxArgs) Sender() (address.Address, error) {
|
||||||
msg, err := tx.OriginalRlpMsg()
|
msg, err := tx.ToRlpUnsignedMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return address.Undef, err
|
return address.Undef, err
|
||||||
}
|
}
|
||||||
@ -274,17 +333,11 @@ func (tx *EthTxArgs) Sender() (address.Address, error) {
|
|||||||
return address.Undef, err
|
return address.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we get an uncompressed public key (that's what we get from the library,
|
ethAddr, err := delegated.EthAddressFromPubKey(pubk)
|
||||||
// but putting this check here for defensiveness), strip the prefix
|
if err != nil {
|
||||||
if pubk[0] == 0x04 {
|
return address.Undef, err
|
||||||
pubk = pubk[1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the f4 address based on the keccak hash of the pubkey.
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write(pubk)
|
|
||||||
ethAddr := hasher.Sum(nil)[12:]
|
|
||||||
|
|
||||||
return address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ethAddr)
|
return address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ethAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,9 +455,9 @@ func parseEip1559Tx(data []byte) (*EthTxArgs, error) {
|
|||||||
GasLimit: gasLimit,
|
GasLimit: gasLimit,
|
||||||
Value: value,
|
Value: value,
|
||||||
Input: input,
|
Input: input,
|
||||||
|
V: v,
|
||||||
R: r,
|
R: r,
|
||||||
S: s,
|
S: s,
|
||||||
V: v,
|
|
||||||
}
|
}
|
||||||
return &args, nil
|
return &args, nil
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/lib/sigs"
|
"github.com/filecoin-project/lotus/lib/sigs"
|
||||||
|
"github.com/filecoin-project/lotus/lib/sigs/delegated"
|
||||||
_ "github.com/filecoin-project/lotus/lib/sigs/delegated"
|
_ "github.com/filecoin-project/lotus/lib/sigs/delegated"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ func TestTxArgs(t *testing.T) {
|
|||||||
txArgs, err := ParseEthTxArgs(tc.Input)
|
txArgs, err := ParseEthTxArgs(tc.Input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
msgRecovered, err := txArgs.OriginalRlpMsg()
|
msgRecovered, err := txArgs.ToRlpUnsignedMsg()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment)
|
require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment)
|
||||||
|
|
||||||
@ -198,14 +199,8 @@ func TestDelegatedSigner(t *testing.T) {
|
|||||||
r := mustDecodeHex(rHex)
|
r := mustDecodeHex(rHex)
|
||||||
s := mustDecodeHex(sHex)
|
s := mustDecodeHex(sHex)
|
||||||
|
|
||||||
if pubk[0] == 0x04 {
|
addrHash, err := delegated.EthAddressFromPubKey(pubk)
|
||||||
pubk = pubk[1:]
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write(pubk)
|
|
||||||
addrHash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash[12:])
|
from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash[12:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package key
|
package key
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
@ -10,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/lib/sigs"
|
"github.com/filecoin-project/lotus/lib/sigs"
|
||||||
|
"github.com/filecoin-project/lotus/lib/sigs/delegated"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKey(typ types.KeyType) (*Key, error) {
|
func GenerateKey(typ types.KeyType) (*Key, error) {
|
||||||
@ -54,17 +54,12 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) {
|
|||||||
}
|
}
|
||||||
case types.KTDelegated:
|
case types.KTDelegated:
|
||||||
// Assume eth for now
|
// Assume eth for now
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
ethAddr, err := delegated.EthAddressFromPubKey(k.PublicKey)
|
||||||
pubk := k.PublicKey
|
if err != nil {
|
||||||
// if we get an uncompressed public key (that's what we get from the library,
|
return nil, xerrors.Errorf("failed to calculate Eth address from public key: %w", err)
|
||||||
// but putting this check here for defensiveness), strip the prefix
|
|
||||||
if pubk[0] == 0x04 {
|
|
||||||
pubk = pubk[1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher.Write(pubk)
|
k.Address, err = address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, ethAddr)
|
||||||
|
|
||||||
k.Address, err = address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, hasher.Sum(nil)[12:])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("converting Delegated to address: %w", err)
|
return nil, xerrors.Errorf("converting Delegated to address: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func TestEthAccountAbstraction(t *testing.T) {
|
|||||||
txArgs, err := ethtypes.NewEthTxArgsFromMessage(msgFromEmbryo)
|
txArgs, err := ethtypes.NewEthTxArgsFromMessage(msgFromEmbryo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
digest, err := txArgs.OriginalRlpMsg()
|
digest, err := txArgs.ToRlpUnsignedMsg()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
siggy, err := client.WalletSign(ctx, embryoAddress, digest)
|
siggy, err := client.WalletSign(ctx, embryoAddress, digest)
|
||||||
@ -107,7 +107,7 @@ func TestEthAccountAbstraction(t *testing.T) {
|
|||||||
txArgs, err = ethtypes.NewEthTxArgsFromMessage(msgFromEmbryo)
|
txArgs, err = ethtypes.NewEthTxArgsFromMessage(msgFromEmbryo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
digest, err = txArgs.OriginalRlpMsg()
|
digest, err = txArgs.ToRlpUnsignedMsg()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
siggy, err = client.WalletSign(ctx, embryoAddress, digest)
|
siggy, err = client.WalletSign(ctx, embryoAddress, digest)
|
||||||
|
136
itests/eth_deploy_test.go
Normal file
136
itests/eth_deploy_test.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package itests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
|
"github.com/filecoin-project/go-state-types/manifest"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/build"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||||
|
"github.com/filecoin-project/lotus/itests/kit"
|
||||||
|
"github.com/filecoin-project/lotus/node/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDeployment smoke tests the deployment of a contract via the
|
||||||
|
// Ethereum JSON-RPC endpoint, from an EEOA.
|
||||||
|
func TestDeployment(t *testing.T) {
|
||||||
|
// TODO the contract installation and invocation can be lifted into utility methods
|
||||||
|
// He who writes the second test, shall do that.
|
||||||
|
// kit.QuietMiningLogs()
|
||||||
|
|
||||||
|
blockTime := 100 * time.Millisecond
|
||||||
|
client, _, ens := kit.EnsembleMinimal(
|
||||||
|
t,
|
||||||
|
kit.MockProofs(),
|
||||||
|
kit.ThroughRPC(),
|
||||||
|
kit.WithCfgOpt(func(cfg *config.FullNode) error {
|
||||||
|
cfg.ActorEvent.EnableRealTimeFilterAPI = true
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
ens.InterconnectAll().BeginMining(blockTime)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// install contract
|
||||||
|
contractHex, err := os.ReadFile("./contracts/SimpleCoin.bin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
contract, err := hex.DecodeString(string(contractHex))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create a new Ethereum account
|
||||||
|
key, ethAddr, deployer := client.EVM().NewAccount()
|
||||||
|
|
||||||
|
// send some funds to the f410 address
|
||||||
|
kit.SendFunds(ctx, t, client, deployer, types.FromFil(10))
|
||||||
|
|
||||||
|
// verify balances.
|
||||||
|
bal := client.EVM().AssertAddressBalanceConsistent(ctx, deployer)
|
||||||
|
require.Equal(t, types.FromFil(10), bal)
|
||||||
|
|
||||||
|
// verify the deployer address is an embryo.
|
||||||
|
client.AssertActorType(ctx, deployer, manifest.EmbryoKey)
|
||||||
|
|
||||||
|
gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{
|
||||||
|
From: ðAddr,
|
||||||
|
Data: contract,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// now deploy a contract from the embryo, and validate it went well
|
||||||
|
tx := ethtypes.EthTxArgs{
|
||||||
|
ChainID: build.Eip155ChainId,
|
||||||
|
Value: big.Zero(),
|
||||||
|
Nonce: 0,
|
||||||
|
MaxFeePerGas: types.NanoFil,
|
||||||
|
MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas),
|
||||||
|
GasLimit: int(gaslimit),
|
||||||
|
Input: contract,
|
||||||
|
V: big.Zero(),
|
||||||
|
R: big.Zero(),
|
||||||
|
S: big.Zero(),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.EVM().SignTransaction(&tx, key.PrivateKey)
|
||||||
|
|
||||||
|
pendingFilter, err := client.EthNewPendingTransactionFilter(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hash := client.EVM().SubmitTransaction(ctx, &tx)
|
||||||
|
fmt.Println(hash)
|
||||||
|
|
||||||
|
changes, err := client.EthGetFilterChanges(ctx, pendingFilter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, changes.Results, 1)
|
||||||
|
require.Equal(t, hash.String(), changes.Results[0])
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
var receipt *api.EthTxReceipt
|
||||||
|
for i := 0; i < 10000000000; i++ {
|
||||||
|
receipt, err = client.EthGetTransactionReceipt(ctx, hash)
|
||||||
|
fmt.Println(receipt, err)
|
||||||
|
if err != nil || receipt == nil {
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, receipt)
|
||||||
|
|
||||||
|
// Success.
|
||||||
|
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
|
||||||
|
|
||||||
|
// Verify that the deployer is now an account.
|
||||||
|
client.AssertActorType(ctx, deployer, manifest.EthAccountKey)
|
||||||
|
|
||||||
|
// Verify that the nonce was incremented.
|
||||||
|
nonce, err := client.MpoolGetNonce(ctx, deployer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, nonce)
|
||||||
|
|
||||||
|
// Verify that the deployer is now an account.
|
||||||
|
client.AssertActorType(ctx, deployer, manifest.EthAccountKey)
|
||||||
|
|
||||||
|
// Get contract address.
|
||||||
|
contractAddr, err := client.EVM().ComputeContractAddress(ethAddr, 0).ToFilecoinAddress()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client.AssertActorType(ctx, contractAddr, "evm")
|
||||||
|
}
|
@ -7,8 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/multiformats/go-varint"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
cbg "github.com/whyrusleeping/cbor-gen"
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
|
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
|
||||||
@ -16,10 +18,15 @@ import (
|
|||||||
"github.com/filecoin-project/go-state-types/big"
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||||
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
||||||
|
"github.com/filecoin-project/go-state-types/crypto"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||||
|
"github.com/filecoin-project/lotus/chain/wallet/key"
|
||||||
|
"github.com/filecoin-project/lotus/lib/sigs"
|
||||||
|
"github.com/filecoin-project/lotus/lib/sigs/delegated"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EVM groups EVM-related actions.
|
// EVM groups EVM-related actions.
|
||||||
@ -122,6 +129,89 @@ func (e *EVM) LoadEvents(ctx context.Context, eventsRoot cid.Cid) []types.Event
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EVM) NewAccount() (*key.Key, ethtypes.EthAddress, address.Address) {
|
||||||
|
// Generate a secp256k1 key; this will back the Ethereum identity.
|
||||||
|
key, err := key.GenerateKey(types.KTSecp256k1)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
ethAddr, err := delegated.EthAddressFromPubKey(key.PublicKey)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ethAddr)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
return key, *(*ethtypes.EthAddress)(ethAddr), addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertAddressBalanceConsistent checks that the balance reported via the
|
||||||
|
// Filecoin and Ethereum operations for an f410 address is identical, returning
|
||||||
|
// the balance.
|
||||||
|
func (e *EVM) AssertAddressBalanceConsistent(ctx context.Context, addr address.Address) big.Int {
|
||||||
|
// Validate the arg is an f410 address.
|
||||||
|
require.Equal(e.t, address.Delegated, addr.Protocol())
|
||||||
|
payload := addr.Payload()
|
||||||
|
namespace, _, err := varint.FromUvarint(payload)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
require.Equal(e.t, builtintypes.EthereumAddressManagerActorID, namespace)
|
||||||
|
|
||||||
|
fbal, err := e.WalletBalance(ctx, addr)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(addr)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
ebal, err := e.EthGetBalance(ctx, ethAddr, "latest")
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
require.Equal(e.t, fbal, types.BigInt(ebal))
|
||||||
|
return fbal
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTransaction signs an Ethereum transaction in place with the supplied private key.
|
||||||
|
func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) {
|
||||||
|
preimage, err := tx.ToRlpUnsignedMsg()
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
// sign the RLP payload
|
||||||
|
signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
r, s, v, err := ethtypes.RecoverSignature(*signature)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
tx.V = big.Int(v)
|
||||||
|
tx.R = big.Int(r)
|
||||||
|
tx.S = big.Int(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitTransaction submits the transaction via the Eth endpoint.
|
||||||
|
func (e *EVM) SubmitTransaction(ctx context.Context, tx *ethtypes.EthTxArgs) ethtypes.EthHash {
|
||||||
|
signed, err := tx.ToRlpSignedMsg()
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
hash, err := e.EthSendRawTransaction(ctx, signed)
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeContractAddress computes the address of a contract deployed by the
|
||||||
|
// specified address with the specified nonce.
|
||||||
|
func (e *EVM) ComputeContractAddress(deployer ethtypes.EthAddress, nonce uint64) ethtypes.EthAddress {
|
||||||
|
nonceRlp, err := formatInt(int(nonce))
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
encoded, err := ethtypes.EncodeRLP([]interface{}{
|
||||||
|
deployer[:],
|
||||||
|
nonceRlp,
|
||||||
|
})
|
||||||
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
hasher.Write(encoded)
|
||||||
|
return *(*ethtypes.EthAddress)(hasher.Sum(nil)[12:])
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: cleanup and put somewhere reusable.
|
// TODO: cleanup and put somewhere reusable.
|
||||||
type apiIpldStore struct {
|
type apiIpldStore struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -152,3 +242,23 @@ func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) err
|
|||||||
func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
||||||
panic("No mutations allowed")
|
panic("No mutations allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatInt(val int) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := binary.Write(buf, binary.BigEndian, int64(val))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return removeLeadingZeros(buf.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeLeadingZeros(data []byte) []byte {
|
||||||
|
firstNonZeroIndex := len(data)
|
||||||
|
for i, b := range data {
|
||||||
|
if b > 0 {
|
||||||
|
firstNonZeroIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data[firstNonZeroIndex:]
|
||||||
|
}
|
||||||
|
33
itests/kit/state.go
Normal file
33
itests/kit/state.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package kit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
actorstypes "github.com/filecoin-project/go-state-types/actors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssertActorType verifies that the supplied address is an actor of the
|
||||||
|
// specified type (as per its manifest key).
|
||||||
|
func (f *TestFullNode) AssertActorType(ctx context.Context, addr address.Address, actorType string) {
|
||||||
|
// validate that an embryo was created
|
||||||
|
act, err := f.StateGetActor(ctx, addr, types.EmptyTSK)
|
||||||
|
require.NoError(f.t, err)
|
||||||
|
|
||||||
|
nv, err := f.StateNetworkVersion(ctx, types.EmptyTSK)
|
||||||
|
require.NoError(f.t, err)
|
||||||
|
|
||||||
|
av, err := actorstypes.VersionForNetwork(nv)
|
||||||
|
require.NoError(f.t, err)
|
||||||
|
|
||||||
|
codecid, exists := actors.GetActorCodeID(av, actorType)
|
||||||
|
require.True(f.t, exists)
|
||||||
|
|
||||||
|
// check the code CID
|
||||||
|
require.Equal(f.t, codecid, act.Code)
|
||||||
|
}
|
26
lib/sigs/delegated/eth.go
Normal file
26
lib/sigs/delegated/eth.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package delegated
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EthAddressFromPubKey returns the Ethereum address corresponding to an
|
||||||
|
// uncompressed secp256k1 public key.
|
||||||
|
//
|
||||||
|
// TODO move somewhere else, this likely doesn't belong here.
|
||||||
|
func EthAddressFromPubKey(pubk []byte) ([]byte, error) {
|
||||||
|
// if we get an uncompressed public key (that's what we get from the library,
|
||||||
|
// but putting this check here for defensiveness), strip the prefix
|
||||||
|
if pubk[0] != 0x04 {
|
||||||
|
return nil, fmt.Errorf("expected first byte of secp256k1 to be 0x04 (uncompressed)")
|
||||||
|
}
|
||||||
|
pubk = pubk[1:]
|
||||||
|
|
||||||
|
// Calculate the Ethereum address based on the keccak hash of the pubkey.
|
||||||
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
hasher.Write(pubk)
|
||||||
|
ethAddr := hasher.Sum(nil)[12:]
|
||||||
|
return ethAddr, nil
|
||||||
|
}
|
@ -228,7 +228,7 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype
|
|||||||
// first, try to get the cid from mined transactions
|
// first, try to get the cid from mined transactions
|
||||||
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true)
|
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.ChainAPI, a.StateAPI)
|
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &tx, nil
|
return &tx, nil
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtype
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.ChainAPI, a.StateAPI)
|
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -1364,7 +1364,7 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx
|
|||||||
gasUsed += msgLookup.Receipt.GasUsed
|
gasUsed += msgLookup.Receipt.GasUsed
|
||||||
|
|
||||||
if fullTxInfo {
|
if fullTxInfo {
|
||||||
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, ca, sa)
|
tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ethtypes.EthBlock{}, nil
|
return ethtypes.EthBlock{}, nil
|
||||||
}
|
}
|
||||||
@ -1498,7 +1498,7 @@ func newEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage,
|
|||||||
// newEthTxFromFilecoinMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed
|
// newEthTxFromFilecoinMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed
|
||||||
// into the function, it looksup the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the
|
// into the function, it looksup the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the
|
||||||
// function
|
// function
|
||||||
func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, ca ChainAPI, sa StateAPI) (ethtypes.EthTx, error) {
|
func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) {
|
||||||
if msgLookup == nil {
|
if msgLookup == nil {
|
||||||
return ethtypes.EthTx{}, fmt.Errorf("msg does not exist")
|
return ethtypes.EthTx{}, fmt.Errorf("msg does not exist")
|
||||||
}
|
}
|
||||||
@ -1533,6 +1533,7 @@ func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLo
|
|||||||
for i, msg := range msgs {
|
for i, msg := range msgs {
|
||||||
if msg.Cid() == msgLookup.Message {
|
if msg.Cid() == msgLookup.Message {
|
||||||
txIdx = i
|
txIdx = i
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if txIdx < 0 {
|
if txIdx < 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user