forked from cerc-io/laconicd-deprecated
update fork
This commit is contained in:
@@ -7,6 +7,12 @@ import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@@ -228,6 +234,11 @@ func traverseFields(
|
||||
// then continue as normal
|
||||
}
|
||||
|
||||
// If its a nil pointer, do not include in types
|
||||
if fieldType.Kind() == reflect.Ptr && field.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
for {
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
@@ -292,6 +303,11 @@ func traverseFields(
|
||||
|
||||
ethTyp := typToEth(fieldType)
|
||||
if len(ethTyp) > 0 {
|
||||
// Support array of uint64
|
||||
if isCollection && fieldType.Kind() != reflect.Slice && fieldType.Kind() != reflect.Array {
|
||||
ethTyp += "[]"
|
||||
}
|
||||
|
||||
if prefix == typeDefPrefix {
|
||||
typeMap[rootType] = append(typeMap[rootType], apitypes.Type{
|
||||
Name: fieldName,
|
||||
@@ -309,7 +325,6 @@ func traverseFields(
|
||||
}
|
||||
|
||||
if fieldType.Kind() == reflect.Struct {
|
||||
|
||||
var fieldTypedef string
|
||||
|
||||
if isCollection {
|
||||
@@ -355,6 +370,7 @@ func jsonNameFromTag(tag reflect.StructTag) string {
|
||||
func sanitizeTypedef(str string) string {
|
||||
buf := new(bytes.Buffer)
|
||||
parts := strings.Split(str, ".")
|
||||
caser := cases.Title(language.English, cases.NoLower)
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "_" {
|
||||
@@ -364,7 +380,7 @@ func sanitizeTypedef(str string) string {
|
||||
|
||||
subparts := strings.Split(part, "_")
|
||||
for _, subpart := range subparts {
|
||||
buf.WriteString(strings.Title(subpart))
|
||||
buf.WriteString(caser.String(subpart))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,8 +391,12 @@ var (
|
||||
hashType = reflect.TypeOf(common.Hash{})
|
||||
addressType = reflect.TypeOf(common.Address{})
|
||||
bigIntType = reflect.TypeOf(big.Int{})
|
||||
cosmIntType = reflect.TypeOf(sdk.Int{})
|
||||
cosmIntType = reflect.TypeOf(sdkmath.Int{})
|
||||
cosmDecType = reflect.TypeOf(sdk.Dec{})
|
||||
cosmosAnyType = reflect.TypeOf(&codectypes.Any{})
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
|
||||
edType = reflect.TypeOf(ed25519.PubKey{})
|
||||
)
|
||||
|
||||
// typToEth supports only basic types and arrays of basic types.
|
||||
@@ -421,6 +441,9 @@ func typToEth(typ reflect.Type) string {
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if typ.Elem().ConvertibleTo(bigIntType) ||
|
||||
typ.Elem().ConvertibleTo(timeType) ||
|
||||
typ.Elem().ConvertibleTo(edType) ||
|
||||
typ.Elem().ConvertibleTo(cosmDecType) ||
|
||||
typ.Elem().ConvertibleTo(cosmIntType) {
|
||||
return str
|
||||
}
|
||||
@@ -428,6 +451,9 @@ func typToEth(typ reflect.Type) string {
|
||||
if typ.ConvertibleTo(hashType) ||
|
||||
typ.ConvertibleTo(addressType) ||
|
||||
typ.ConvertibleTo(bigIntType) ||
|
||||
typ.ConvertibleTo(edType) ||
|
||||
typ.ConvertibleTo(timeType) ||
|
||||
typ.ConvertibleTo(cosmDecType) ||
|
||||
typ.ConvertibleTo(cosmIntType) {
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package eip712
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cerc-io/laconicd/types"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
cosmoskr "github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
)
|
||||
|
||||
// PreprocessLedgerTx reformats Ledger-signed Cosmos transactions to match the fork expected by Ethermint
|
||||
// by including the signature in a Web3Tx extension and sending a blank signature in the body.
|
||||
func PreprocessLedgerTx(chainID string, keyType cosmoskr.KeyType, txBuilder client.TxBuilder) error {
|
||||
// Only process Ledger transactions
|
||||
if keyType != cosmoskr.TypeLedger {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init extension builder to set Web3 extension
|
||||
extensionBuilder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot cast TxBuilder to ExtensionOptionsTxBuilder")
|
||||
}
|
||||
|
||||
// Get signatures from TxBuilder
|
||||
sigs, err := txBuilder.GetTx().GetSignaturesV2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get signatures: %w", err)
|
||||
}
|
||||
|
||||
// Verify single-signer
|
||||
if len(sigs) != 1 {
|
||||
return fmt.Errorf("invalid number of signatures, expected 1 and got %v", len(sigs))
|
||||
}
|
||||
|
||||
signature := sigs[0]
|
||||
sigData, ok := signature.Data.(*signing.SingleSignatureData)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected signature type, expected SingleSignatureData")
|
||||
}
|
||||
sigBytes := sigData.Signature
|
||||
|
||||
// Parse Chain ID as big.Int
|
||||
chainIDInt, err := types.ParseChainID(chainID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse chain id: %w", err)
|
||||
}
|
||||
|
||||
// Add ExtensionOptionsWeb3Tx extension with signature
|
||||
var option *codectypes.Any
|
||||
option, err = codectypes.NewAnyWithValue(&types.ExtensionOptionsWeb3Tx{
|
||||
FeePayer: txBuilder.GetTx().FeePayer().String(),
|
||||
TypedDataChainID: chainIDInt.Uint64(),
|
||||
FeePayerSig: sigBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set extension as any: %w", err)
|
||||
}
|
||||
|
||||
extensionBuilder.SetExtensionOptions(option)
|
||||
|
||||
// Set blank signature with Amino Sign Type
|
||||
// (Regardless of input signMode, Evmos requires Amino signature type for Ledger)
|
||||
blankSig := signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
|
||||
Signature: nil,
|
||||
}
|
||||
sig := signing.SignatureV2{
|
||||
PubKey: signature.PubKey,
|
||||
Data: &blankSig,
|
||||
Sequence: signature.Sequence,
|
||||
}
|
||||
|
||||
err = txBuilder.SetSignatures(sig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set signatures on payload: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package eip712_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"github.com/cerc-io/laconicd/app"
|
||||
"github.com/cerc-io/laconicd/encoding"
|
||||
"github.com/cerc-io/laconicd/ethereum/eip712"
|
||||
"github.com/cerc-io/laconicd/tests"
|
||||
"github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Testing Constants
|
||||
var chainId = "ethermint_9000-1"
|
||||
var ctx = client.Context{}.WithTxConfig(
|
||||
encoding.MakeConfig(app.ModuleBasics).TxConfig,
|
||||
)
|
||||
var feePayerAddress = "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"
|
||||
|
||||
type TestCaseStruct struct {
|
||||
txBuilder client.TxBuilder
|
||||
expectedFeePayer string
|
||||
expectedGas uint64
|
||||
expectedFee math.Int
|
||||
expectedMemo string
|
||||
expectedMsg string
|
||||
expectedSignatureBytes []byte
|
||||
}
|
||||
|
||||
func TestLedgerPreprocessing(t *testing.T) {
|
||||
// Update bech32 prefix
|
||||
sdk.GetConfig().SetBech32PrefixForAccount("ethm", "")
|
||||
|
||||
testCases := []TestCaseStruct{
|
||||
createBasicTestCase(t),
|
||||
createPopulatedTestCase(t),
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// Run pre-processing
|
||||
err := eip712.PreprocessLedgerTx(
|
||||
chainId,
|
||||
keyring.TypeLedger,
|
||||
tc.txBuilder,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify Web3 extension matches expected
|
||||
hasExtOptsTx, ok := tc.txBuilder.(ante.HasExtensionOptionsTx)
|
||||
require.True(t, ok)
|
||||
require.True(t, len(hasExtOptsTx.GetExtensionOptions()) == 1)
|
||||
|
||||
expectedExt := types.ExtensionOptionsWeb3Tx{
|
||||
TypedDataChainID: 9000,
|
||||
FeePayer: feePayerAddress,
|
||||
FeePayerSig: tc.expectedSignatureBytes,
|
||||
}
|
||||
|
||||
expectedExtAny, err := codectypes.NewAnyWithValue(&expectedExt)
|
||||
require.NoError(t, err)
|
||||
|
||||
actualExtAny := hasExtOptsTx.GetExtensionOptions()[0]
|
||||
require.Equal(t, expectedExtAny, actualExtAny)
|
||||
|
||||
// Verify signature type matches expected
|
||||
signatures, err := tc.txBuilder.GetTx().GetSignaturesV2()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(signatures), 1)
|
||||
|
||||
txSig := signatures[0].Data.(*signing.SingleSignatureData)
|
||||
require.Equal(t, txSig.SignMode, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
|
||||
|
||||
// Verify signature is blank
|
||||
require.Equal(t, len(txSig.Signature), 0)
|
||||
|
||||
// Verify tx fields are unchanged
|
||||
tx := tc.txBuilder.GetTx()
|
||||
|
||||
require.Equal(t, tx.FeePayer().String(), tc.expectedFeePayer)
|
||||
require.Equal(t, tx.GetGas(), tc.expectedGas)
|
||||
require.Equal(t, tx.GetFee().AmountOf(evmtypes.DefaultParams().EvmDenom), tc.expectedFee)
|
||||
require.Equal(t, tx.GetMemo(), tc.expectedMemo)
|
||||
|
||||
// Verify message is unchanged
|
||||
if tc.expectedMsg != "" {
|
||||
require.Equal(t, len(tx.GetMsgs()), 1)
|
||||
require.Equal(t, tx.GetMsgs()[0].String(), tc.expectedMsg)
|
||||
} else {
|
||||
require.Equal(t, len(tx.GetMsgs()), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlankTxBuilder(t *testing.T) {
|
||||
txBuilder := ctx.TxConfig.NewTxBuilder()
|
||||
|
||||
err := eip712.PreprocessLedgerTx(
|
||||
chainId,
|
||||
keyring.TypeLedger,
|
||||
txBuilder,
|
||||
)
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNonLedgerTxBuilder(t *testing.T) {
|
||||
txBuilder := ctx.TxConfig.NewTxBuilder()
|
||||
|
||||
err := eip712.PreprocessLedgerTx(
|
||||
chainId,
|
||||
keyring.TypeLocal,
|
||||
txBuilder,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInvalidChainId(t *testing.T) {
|
||||
txBuilder := ctx.TxConfig.NewTxBuilder()
|
||||
|
||||
err := eip712.PreprocessLedgerTx(
|
||||
"invalid-chain-id",
|
||||
keyring.TypeLedger,
|
||||
txBuilder,
|
||||
)
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func createBasicTestCase(t *testing.T) TestCaseStruct {
|
||||
t.Helper()
|
||||
txBuilder := ctx.TxConfig.NewTxBuilder()
|
||||
|
||||
feePayer, err := sdk.AccAddressFromBech32(feePayerAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBuilder.SetFeePayer(feePayer)
|
||||
|
||||
// Create signature unrelated to payload for testing
|
||||
signatureHex := strings.Repeat("01", 65)
|
||||
signatureBytes, err := hex.DecodeString(signatureHex)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, privKey := tests.NewAddrKey()
|
||||
sigsV2 := signing.SignatureV2{
|
||||
PubKey: privKey.PubKey(), // Use unrelated public key for testing
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: signatureBytes,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
txBuilder.SetSignatures(sigsV2)
|
||||
return TestCaseStruct{
|
||||
txBuilder: txBuilder,
|
||||
expectedFeePayer: feePayer.String(),
|
||||
expectedGas: 0,
|
||||
expectedFee: math.NewInt(0),
|
||||
expectedMemo: "",
|
||||
expectedMsg: "",
|
||||
expectedSignatureBytes: signatureBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func createPopulatedTestCase(t *testing.T) TestCaseStruct {
|
||||
t.Helper()
|
||||
basicTestCase := createBasicTestCase(t)
|
||||
txBuilder := basicTestCase.txBuilder
|
||||
|
||||
gasLimit := uint64(200000)
|
||||
memo := ""
|
||||
denom := evmtypes.DefaultParams().EvmDenom
|
||||
feeAmount := math.NewInt(2000)
|
||||
|
||||
txBuilder.SetFeeAmount(sdk.NewCoins(
|
||||
sdk.NewCoin(
|
||||
denom,
|
||||
feeAmount,
|
||||
)))
|
||||
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
txBuilder.SetMemo(memo)
|
||||
|
||||
msgSend := banktypes.MsgSend{
|
||||
FromAddress: feePayerAddress,
|
||||
ToAddress: "ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa",
|
||||
Amount: sdk.NewCoins(
|
||||
sdk.NewCoin(
|
||||
evmtypes.DefaultParams().EvmDenom,
|
||||
math.NewInt(10000000),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
txBuilder.SetMsgs(&msgSend)
|
||||
|
||||
return TestCaseStruct{
|
||||
txBuilder: txBuilder,
|
||||
expectedFeePayer: basicTestCase.expectedFeePayer,
|
||||
expectedGas: gasLimit,
|
||||
expectedFee: feeAmount,
|
||||
expectedMemo: memo,
|
||||
expectedMsg: msgSend.String(),
|
||||
expectedSignatureBytes: basicTestCase.expectedSignatureBytes,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user