github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/types/account.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/tendermint/go-amino"
     9  	"gopkg.in/yaml.v2"
    10  
    11  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    12  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    13  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth"
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/exported"
    15  	authtypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/types"
    16  
    17  	ethcmn "github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/crypto"
    19  	ethcrypto "github.com/ethereum/go-ethereum/crypto"
    20  )
    21  
    22  var _ exported.Account = (*EthAccount)(nil)
    23  var _ exported.GenesisAccount = (*EthAccount)(nil)
    24  var emptyCodeHash = crypto.Keccak256(nil)
    25  
    26  func init() {
    27  	authtypes.RegisterAccountTypeCodec(&EthAccount{}, EthAccountName)
    28  }
    29  
    30  // ----------------------------------------------------------------------------
    31  // Main FBchain account
    32  // ----------------------------------------------------------------------------
    33  
    34  // EthAccount implements the auth.Account interface and embeds an
    35  // auth.BaseAccount type. It is compatible with the auth.AccountKeeper.
    36  type EthAccount struct {
    37  	*authtypes.BaseAccount `json:"base_account" yaml:"base_account"`
    38  	CodeHash               []byte `json:"code_hash" yaml:"code_hash"`
    39  }
    40  
    41  func (acc *EthAccount) UnmarshalFromAmino(cdc *amino.Codec, data []byte) error {
    42  	var dataLen uint64 = 0
    43  	var baseAccountFlag bool
    44  
    45  	for {
    46  		data = data[dataLen:]
    47  
    48  		if len(data) <= 0 {
    49  			break
    50  		}
    51  
    52  		pos, pbType, err := amino.ParseProtoPosAndTypeMustOneByte(data[0])
    53  		if err != nil {
    54  			return err
    55  		}
    56  		// all EthAccount fields are (2)
    57  		if pbType != amino.Typ3_ByteLength {
    58  			return fmt.Errorf("invalid pbType: %v", pbType)
    59  		}
    60  		data = data[1:]
    61  
    62  		var n int
    63  		dataLen, n, err = amino.DecodeUvarint(data)
    64  		if err != nil {
    65  			return err
    66  		}
    67  
    68  		data = data[n:]
    69  		if len(data) < int(dataLen) {
    70  			return fmt.Errorf("not enough data for field %d", pos)
    71  		}
    72  		subData := data[:dataLen]
    73  
    74  		switch pos {
    75  		case 1:
    76  			baseAccountFlag = true
    77  			if acc.BaseAccount == nil {
    78  				acc.BaseAccount = &auth.BaseAccount{}
    79  			} else {
    80  				*acc.BaseAccount = auth.BaseAccount{}
    81  			}
    82  			err = acc.BaseAccount.UnmarshalFromAmino(cdc, subData)
    83  			if err != nil {
    84  				return err
    85  			}
    86  		case 2:
    87  			acc.CodeHash = make([]byte, len(subData))
    88  			copy(acc.CodeHash, subData)
    89  		default:
    90  			return fmt.Errorf("unexpect feild num %d", pos)
    91  		}
    92  	}
    93  	if !baseAccountFlag {
    94  		acc.BaseAccount = nil
    95  	}
    96  	return nil
    97  }
    98  
    99  type componentAccount struct {
   100  	ethAccount  EthAccount
   101  	baseAccount authtypes.BaseAccount
   102  }
   103  
   104  func (acc EthAccount) Copy() sdk.Account {
   105  	// we need only allocate one object on the heap with componentAccount
   106  	var cacc componentAccount
   107  
   108  	cacc.baseAccount.Address = acc.Address
   109  	cacc.baseAccount.Coins = acc.Coins
   110  	cacc.baseAccount.PubKey = acc.PubKey
   111  	cacc.baseAccount.AccountNumber = acc.AccountNumber
   112  	cacc.baseAccount.Sequence = acc.Sequence
   113  
   114  	cacc.ethAccount.BaseAccount = &cacc.baseAccount
   115  	cacc.ethAccount.CodeHash = acc.CodeHash
   116  
   117  	return &cacc.ethAccount
   118  }
   119  
   120  func (acc EthAccount) AminoSize(cdc *amino.Codec) int {
   121  	size := 0
   122  	if acc.BaseAccount != nil {
   123  		baccSize := acc.BaseAccount.AminoSize(cdc)
   124  		size += 1 + amino.UvarintSize(uint64(baccSize)) + baccSize
   125  	}
   126  	if len(acc.CodeHash) != 0 {
   127  		size += 1 + amino.ByteSliceSize(acc.CodeHash)
   128  	}
   129  	return size
   130  }
   131  
   132  func (acc EthAccount) MarshalToAmino(cdc *amino.Codec) ([]byte, error) {
   133  	var buf bytes.Buffer
   134  	buf.Grow(acc.AminoSize(cdc))
   135  	err := acc.MarshalAminoTo(cdc, &buf)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	return buf.Bytes(), nil
   140  }
   141  
   142  func (acc EthAccount) MarshalAminoTo(cdc *amino.Codec, buf *bytes.Buffer) error {
   143  	// field 1
   144  	if acc.BaseAccount != nil {
   145  		const pbKey = 1<<3 | 2
   146  		buf.WriteByte(pbKey)
   147  		baccSize := acc.BaseAccount.AminoSize(cdc)
   148  		err := amino.EncodeUvarintToBuffer(buf, uint64(baccSize))
   149  		if err != nil {
   150  			return err
   151  		}
   152  		lenBeforeData := buf.Len()
   153  		err = acc.BaseAccount.MarshalAminoTo(cdc, buf)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		if buf.Len()-lenBeforeData != baccSize {
   158  			return amino.NewSizerError(acc.BaseAccount, baccSize, buf.Len()-lenBeforeData)
   159  		}
   160  	}
   161  
   162  	// field 2
   163  	if len(acc.CodeHash) != 0 {
   164  		const pbKey = 2<<3 | 2
   165  		err := amino.EncodeByteSliceWithKeyToBuffer(buf, acc.CodeHash, pbKey)
   166  		if err != nil {
   167  			return err
   168  		}
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  // ProtoAccount defines the prototype function for BaseAccount used for an
   175  // AccountKeeper.
   176  func ProtoAccount() exported.Account {
   177  	return &EthAccount{
   178  		BaseAccount: &auth.BaseAccount{},
   179  		CodeHash:    ethcrypto.Keccak256(nil),
   180  	}
   181  }
   182  
   183  // EthAddress returns the account address ethereum format.
   184  func (acc EthAccount) EthAddress() ethcmn.Address {
   185  	return ethcmn.BytesToAddress(acc.Address.Bytes())
   186  }
   187  
   188  // TODO: remove on SDK v0.40
   189  
   190  // Balance returns the balance of an account.
   191  func (acc EthAccount) Balance(denom string) sdk.Dec {
   192  	return acc.GetCoins().AmountOf(denom)
   193  }
   194  
   195  // SetBalance sets an account's balance of the given coin denomination.
   196  //
   197  // CONTRACT: assumes the denomination is valid.
   198  func (acc *EthAccount) SetBalance(denom string, amt sdk.Dec) {
   199  	coins := acc.GetCoins()
   200  	diff := amt.Sub(coins.AmountOf(denom))
   201  	switch {
   202  	case diff.IsPositive():
   203  		// Increase coins to amount
   204  		coins = coins.Add(sdk.NewCoin(denom, diff))
   205  	case diff.IsNegative():
   206  		// Decrease coins to amount
   207  		coins = coins.Sub(sdk.NewCoins(sdk.NewCoin(denom, diff.Neg())))
   208  	default:
   209  		return
   210  	}
   211  
   212  	if err := acc.SetCoins(coins); err != nil {
   213  		panic(fmt.Errorf("could not set %s coins for address %s: %w", denom, acc.EthAddress().String(), err))
   214  	}
   215  }
   216  
   217  type ethermintAccountPretty struct {
   218  	Address       sdk.AccAddress `json:"address" yaml:"address"`
   219  	EthAddress    string         `json:"eth_address" yaml:"eth_address"`
   220  	Coins         sdk.Coins      `json:"coins" yaml:"coins"`
   221  	PubKey        string         `json:"public_key" yaml:"public_key"`
   222  	AccountNumber uint64         `json:"account_number" yaml:"account_number"`
   223  	Sequence      uint64         `json:"sequence" yaml:"sequence"`
   224  	CodeHash      string         `json:"code_hash" yaml:"code_hash"`
   225  }
   226  
   227  // MarshalYAML returns the YAML representation of an account.
   228  func (acc EthAccount) MarshalYAML() (interface{}, error) {
   229  	ethAddress := ""
   230  	if !sdk.IsWasmAddress(acc.Address) {
   231  		ethAddress = acc.EthAddress().String()
   232  	}
   233  	alias := ethermintAccountPretty{
   234  		Address:       acc.Address,
   235  		EthAddress:    ethAddress,
   236  		Coins:         acc.Coins,
   237  		AccountNumber: acc.AccountNumber,
   238  		Sequence:      acc.Sequence,
   239  		CodeHash:      ethcmn.Bytes2Hex(acc.CodeHash),
   240  	}
   241  
   242  	var err error
   243  
   244  	if acc.PubKey != nil {
   245  		alias.PubKey, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, acc.PubKey)
   246  		if err != nil {
   247  			return nil, err
   248  		}
   249  	}
   250  
   251  	bz, err := yaml.Marshal(alias)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	return string(bz), err
   257  }
   258  
   259  // MarshalJSON returns the JSON representation of an EthAccount.
   260  func (acc EthAccount) MarshalJSON() ([]byte, error) {
   261  	var ethAddress = ""
   262  
   263  	if acc.BaseAccount != nil && acc.Address != nil {
   264  		if !sdk.IsWasmAddress(acc.Address) {
   265  			ethAddress = acc.EthAddress().String()
   266  		}
   267  	}
   268  
   269  	alias := ethermintAccountPretty{
   270  		Address:       acc.Address,
   271  		EthAddress:    ethAddress,
   272  		Coins:         acc.Coins,
   273  		AccountNumber: acc.AccountNumber,
   274  		Sequence:      acc.Sequence,
   275  		CodeHash:      ethcmn.Bytes2Hex(acc.CodeHash),
   276  	}
   277  
   278  	var err error
   279  
   280  	if acc.PubKey != nil {
   281  		alias.PubKey, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, acc.PubKey)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  	}
   286  
   287  	return json.Marshal(alias)
   288  }
   289  
   290  // UnmarshalJSON unmarshals raw JSON bytes into an EthAccount.
   291  func (acc *EthAccount) UnmarshalJSON(bz []byte) error {
   292  	var (
   293  		alias ethermintAccountPretty
   294  		err   error
   295  	)
   296  
   297  	if err := json.Unmarshal(bz, &alias); err != nil {
   298  		return err
   299  	}
   300  
   301  	switch {
   302  	case !alias.Address.Empty() && alias.EthAddress != "":
   303  		// Both addresses provided. Verify correctness
   304  		ethAddress := ethcmn.HexToAddress(alias.EthAddress)
   305  		ethAddressFromAccAddress := ethcmn.BytesToAddress(alias.Address.Bytes())
   306  
   307  		if !bytes.Equal(ethAddress.Bytes(), alias.Address.Bytes()) {
   308  			err = sdkerrors.Wrapf(
   309  				sdkerrors.ErrInvalidAddress,
   310  				"expected %s, got %s",
   311  				ethAddressFromAccAddress.String(), ethAddress.String(),
   312  			)
   313  		}
   314  
   315  	case !alias.Address.Empty() && alias.EthAddress == "":
   316  		// unmarshal sdk.AccAddress only. Do nothing here
   317  	case alias.Address.Empty() && alias.EthAddress != "":
   318  		// retrieve sdk.AccAddress from ethereum address
   319  		ethAddress := ethcmn.HexToAddress(alias.EthAddress)
   320  		alias.Address = sdk.AccAddress(ethAddress.Bytes())
   321  	case alias.Address.Empty() && alias.EthAddress == "":
   322  		err = sdkerrors.Wrapf(
   323  			sdkerrors.ErrInvalidAddress,
   324  			"account must contain address in Ethereum Hex or Cosmos Bech32 format",
   325  		)
   326  	}
   327  
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	acc.BaseAccount = &authtypes.BaseAccount{
   333  		Coins:         alias.Coins,
   334  		Address:       alias.Address,
   335  		AccountNumber: alias.AccountNumber,
   336  		Sequence:      alias.Sequence,
   337  	}
   338  	acc.CodeHash = ethcmn.Hex2Bytes(alias.CodeHash)
   339  
   340  	if alias.PubKey != "" {
   341  		acc.BaseAccount.PubKey, err = sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeAccPub, alias.PubKey)
   342  		if err != nil {
   343  			return err
   344  		}
   345  	}
   346  	return nil
   347  }
   348  
   349  // String implements the fmt.Stringer interface
   350  func (acc EthAccount) String() string {
   351  	out, _ := yaml.Marshal(acc)
   352  	return string(out)
   353  }
   354  
   355  // IsContract returns if the account contains contract code.
   356  func (acc EthAccount) IsContract() bool {
   357  	return !bytes.Equal(acc.CodeHash, emptyCodeHash)
   358  }