code.vegaprotocol.io/vega@v0.79.0/core/types/asset.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package types
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  
    22  	"code.vegaprotocol.io/vega/libs/crypto"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  	"code.vegaprotocol.io/vega/libs/stringer"
    25  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    26  )
    27  
    28  var (
    29  	ErrMissingERC20ContractAddress     = errors.New("missing erc20 contract address")
    30  	ErrMissingBuiltinAssetField        = errors.New("missing builtin asset field")
    31  	ErrInvalidAssetNameEmpty           = errors.New("invalid asset, name must not be empty")
    32  	ErrInvalidAssetSymbolEmpty         = errors.New("invalid asset, symbol must not be empty")
    33  	ErrInvalidAssetQuantumZero         = errors.New("invalid asset, quantum must not be zero")
    34  	ErrLifetimeLimitMustBePositive     = errors.New("lifetime limit must be positive")
    35  	ErrWithdrawThresholdMustBePositive = errors.New("withdraw threshold must be positive")
    36  )
    37  
    38  type AssetStatus = vegapb.Asset_Status
    39  
    40  const (
    41  	// AssetStatusUnspecified is the default value, always invalid.
    42  	AssetStatusUnspecified AssetStatus = vegapb.Asset_STATUS_UNSPECIFIED
    43  	// AssetStatusProposed states the asset is proposed and under vote.
    44  	AssetStatusProposed AssetStatus = vegapb.Asset_STATUS_PROPOSED
    45  	// AssetStatusRejected states the asset has been rejected from governance.
    46  	AssetStatusRejected AssetStatus = vegapb.Asset_STATUS_REJECTED
    47  	// AssetStatusPendingListing states the asset is pending listing from the bridge.
    48  	AssetStatusPendingListing AssetStatus = vegapb.Asset_STATUS_PENDING_LISTING
    49  	// AssetStatusEnabled states the asset is fully usable in the network.
    50  	AssetStatusEnabled AssetStatus = vegapb.Asset_STATUS_ENABLED
    51  )
    52  
    53  type Asset struct {
    54  	// Internal identifier of the asset
    55  	ID string
    56  	// Details of the asset (e.g: Great British Pound)
    57  	Details *AssetDetails
    58  	// Status of the asset
    59  	Status AssetStatus
    60  }
    61  
    62  type isAssetDetails interface {
    63  	isAssetDetails()
    64  	adIntoProto() interface{}
    65  	DeepClone() isAssetDetails
    66  	Validate() (ProposalError, error)
    67  	String() string
    68  }
    69  
    70  func (a Asset) IntoProto() *vegapb.Asset {
    71  	var details *vegapb.AssetDetails
    72  	if a.Details != nil {
    73  		details = a.Details.IntoProto()
    74  	}
    75  	return &vegapb.Asset{
    76  		Id:      a.ID,
    77  		Details: details,
    78  		Status:  a.Status,
    79  	}
    80  }
    81  
    82  func (a Asset) DeepClone() *Asset {
    83  	cpy := a
    84  	if a.Details == nil {
    85  		return &cpy
    86  	}
    87  	cpy.Details.Quantum = a.Details.Quantum
    88  	if a.Details.Source != nil {
    89  		cpy.Details.Source = a.Details.Source.DeepClone()
    90  	}
    91  	return &cpy
    92  }
    93  
    94  func AssetFromProto(p *vegapb.Asset) (*Asset, error) {
    95  	var (
    96  		details *AssetDetails
    97  		err     error
    98  	)
    99  	if p.Details != nil {
   100  		details, err = AssetDetailsFromProto(p.Details)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  	}
   105  	return &Asset{
   106  		ID:      p.Id,
   107  		Details: details,
   108  		Status:  p.Status,
   109  	}, nil
   110  }
   111  
   112  type AssetDetails struct {
   113  	Name     string
   114  	Symbol   string
   115  	Decimals uint64
   116  	Quantum  num.Decimal
   117  	Source   isAssetDetails
   118  }
   119  
   120  func (a AssetDetails) String() string {
   121  	return fmt.Sprintf(
   122  		"name(%s) symbol(%s) quantum(%s) decimals(%d) source(%s)",
   123  		a.Name,
   124  		a.Symbol,
   125  		a.Quantum.String(),
   126  		a.Decimals,
   127  		stringer.ObjToString(a.Source),
   128  	)
   129  }
   130  
   131  func (a AssetDetails) Validate() (ProposalError, error) {
   132  	if len(a.Name) == 0 {
   133  		return ProposalErrorInvalidAssetDetails, ErrInvalidAssetNameEmpty
   134  	}
   135  
   136  	if len(a.Symbol) == 0 {
   137  		return ProposalErrorInvalidAssetDetails, ErrInvalidAssetSymbolEmpty
   138  	}
   139  
   140  	if a.Quantum.IsZero() {
   141  		return ProposalErrorInvalidAssetDetails, ErrInvalidAssetQuantumZero
   142  	}
   143  
   144  	return ProposalErrorUnspecified, nil
   145  }
   146  
   147  func (a AssetDetails) IntoProto() *vegapb.AssetDetails {
   148  	r := &vegapb.AssetDetails{
   149  		Name:     a.Name,
   150  		Symbol:   a.Symbol,
   151  		Decimals: a.Decimals,
   152  		Quantum:  a.Quantum.String(),
   153  	}
   154  	if a.Source == nil {
   155  		return r
   156  	}
   157  	src := a.Source.adIntoProto()
   158  	switch s := src.(type) {
   159  	case *vegapb.AssetDetails_Erc20:
   160  		r.Source = s
   161  	case *vegapb.AssetDetails_BuiltinAsset:
   162  		r.Source = s
   163  	}
   164  	return r
   165  }
   166  
   167  func (a AssetDetails) GetERC20() *ERC20 {
   168  	switch s := a.Source.(type) {
   169  	case *AssetDetailsErc20:
   170  		return s.ERC20
   171  	default:
   172  		return nil
   173  	}
   174  }
   175  
   176  func (a AssetDetails) DeepClone() *AssetDetails {
   177  	var src isAssetDetails
   178  	if a.Source != nil {
   179  		src = a.Source.DeepClone()
   180  	}
   181  	cpy := &AssetDetails{
   182  		Name:     a.Name,
   183  		Symbol:   a.Symbol,
   184  		Decimals: a.Decimals,
   185  		Source:   src,
   186  	}
   187  	cpy.Quantum = a.Quantum
   188  	return cpy
   189  }
   190  
   191  func AssetDetailsFromProto(p *vegapb.AssetDetails) (*AssetDetails, error) {
   192  	var (
   193  		src isAssetDetails
   194  		err error
   195  	)
   196  	switch st := p.Source.(type) {
   197  	case *vegapb.AssetDetails_Erc20:
   198  		src, err = AssetDetailsERC20FromProto(st)
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  	case *vegapb.AssetDetails_BuiltinAsset:
   203  		src = AssetDetailsBuiltinFromProto(st)
   204  	}
   205  	quantum := num.DecimalZero()
   206  	if len(p.Quantum) > 0 {
   207  		var err error
   208  		quantum, err = num.DecimalFromString(p.Quantum)
   209  		if err != nil {
   210  			return nil, fmt.Errorf("invalid quantum: %w", err)
   211  		}
   212  	}
   213  	return &AssetDetails{
   214  		Name:     p.Name,
   215  		Symbol:   p.Symbol,
   216  		Decimals: p.Decimals,
   217  		Quantum:  quantum,
   218  		Source:   src,
   219  	}, nil
   220  }
   221  
   222  type AssetDetailsBuiltinAsset struct {
   223  	BuiltinAsset *BuiltinAsset
   224  }
   225  
   226  func (a AssetDetailsBuiltinAsset) String() string {
   227  	return fmt.Sprintf(
   228  		"builtinAsset(%s)",
   229  		stringer.PtrToString(a.BuiltinAsset),
   230  	)
   231  }
   232  
   233  func (a AssetDetailsBuiltinAsset) IntoProto() *vegapb.AssetDetails_BuiltinAsset {
   234  	p := &vegapb.AssetDetails_BuiltinAsset{
   235  		BuiltinAsset: &vegapb.BuiltinAsset{},
   236  	}
   237  	if a.BuiltinAsset != nil && a.BuiltinAsset.MaxFaucetAmountMint != nil {
   238  		p.BuiltinAsset.MaxFaucetAmountMint = a.BuiltinAsset.MaxFaucetAmountMint.String()
   239  	}
   240  	return p
   241  }
   242  
   243  func (a AssetDetailsBuiltinAsset) adIntoProto() interface{} {
   244  	return a.IntoProto()
   245  }
   246  
   247  func (AssetDetailsBuiltinAsset) isAssetDetails() {}
   248  
   249  func (a AssetDetailsBuiltinAsset) DeepClone() isAssetDetails {
   250  	cpy := a
   251  	if a.BuiltinAsset == nil {
   252  		return &cpy
   253  	}
   254  	if a.BuiltinAsset.MaxFaucetAmountMint != nil {
   255  		cpy.BuiltinAsset.MaxFaucetAmountMint = a.BuiltinAsset.MaxFaucetAmountMint.Clone()
   256  	}
   257  	return &cpy
   258  }
   259  
   260  func (a AssetDetailsBuiltinAsset) Validate() (ProposalError, error) {
   261  	if a.BuiltinAsset.MaxFaucetAmountMint.IsZero() {
   262  		return ProposalErrorMissingBuiltinAssetField, ErrMissingBuiltinAssetField
   263  	}
   264  	return ProposalErrorUnspecified, nil
   265  }
   266  
   267  func AssetDetailsBuiltinFromProto(p *vegapb.AssetDetails_BuiltinAsset) *AssetDetailsBuiltinAsset {
   268  	if p.BuiltinAsset.MaxFaucetAmountMint != "" {
   269  		max, _ := num.UintFromString(p.BuiltinAsset.MaxFaucetAmountMint, 10)
   270  		return &AssetDetailsBuiltinAsset{
   271  			BuiltinAsset: &BuiltinAsset{
   272  				MaxFaucetAmountMint: max,
   273  			},
   274  		}
   275  	}
   276  	return &AssetDetailsBuiltinAsset{}
   277  }
   278  
   279  // BuiltinAsset is a Vega internal asset.
   280  type BuiltinAsset struct {
   281  	MaxFaucetAmountMint *num.Uint
   282  }
   283  
   284  func (a BuiltinAsset) String() string {
   285  	return fmt.Sprintf(
   286  		"maxFaucetAmountMint(%s)",
   287  		stringer.PtrToString(a.MaxFaucetAmountMint),
   288  	)
   289  }
   290  
   291  type AssetDetailsErc20 struct {
   292  	ERC20 *ERC20
   293  }
   294  
   295  func (a AssetDetailsErc20) String() string {
   296  	return fmt.Sprintf(
   297  		"erc20(%s)",
   298  		stringer.PtrToString(a.ERC20),
   299  	)
   300  }
   301  
   302  func (a AssetDetailsErc20) IntoProto() *vegapb.AssetDetails_Erc20 {
   303  	lifetimeLimit := "0"
   304  	if a.ERC20.LifetimeLimit != nil {
   305  		lifetimeLimit = a.ERC20.LifetimeLimit.String()
   306  	}
   307  	withdrawThreshold := "0"
   308  	if a.ERC20.WithdrawThreshold != nil {
   309  		withdrawThreshold = a.ERC20.WithdrawThreshold.String()
   310  	}
   311  	return &vegapb.AssetDetails_Erc20{
   312  		Erc20: &vegapb.ERC20{
   313  			ContractAddress:   a.ERC20.ContractAddress,
   314  			ChainId:           a.ERC20.ChainID,
   315  			LifetimeLimit:     lifetimeLimit,
   316  			WithdrawThreshold: withdrawThreshold,
   317  		},
   318  	}
   319  }
   320  
   321  func (a AssetDetailsErc20) adIntoProto() interface{} {
   322  	return a.IntoProto()
   323  }
   324  
   325  func (AssetDetailsErc20) isAssetDetails() {}
   326  
   327  func (a AssetDetailsErc20) DeepClone() isAssetDetails {
   328  	if a.ERC20 == nil {
   329  		return &AssetDetailsErc20{}
   330  	}
   331  	return &AssetDetailsErc20{
   332  		ERC20: a.ERC20.DeepClone(),
   333  	}
   334  }
   335  
   336  func (a AssetDetailsErc20) Validate() (ProposalError, error) {
   337  	if len(a.ERC20.ContractAddress) <= 0 {
   338  		return ProposalErrorMissingErc20ContractAddress, ErrMissingERC20ContractAddress
   339  	}
   340  
   341  	return ProposalErrorUnspecified, nil
   342  }
   343  
   344  func AssetDetailsERC20FromProto(p *vegapb.AssetDetails_Erc20) (*AssetDetailsErc20, error) {
   345  	var (
   346  		lifetimeLimit     = num.UintZero()
   347  		withdrawThreshold = num.UintZero()
   348  		overflow          bool
   349  	)
   350  	if len(p.Erc20.LifetimeLimit) > 0 {
   351  		lifetimeLimit, overflow = num.UintFromString(p.Erc20.LifetimeLimit, 10)
   352  		if overflow {
   353  			return nil, errors.New("invalid lifetime limit")
   354  		}
   355  	}
   356  	if len(p.Erc20.WithdrawThreshold) > 0 {
   357  		withdrawThreshold, overflow = num.UintFromString(p.Erc20.WithdrawThreshold, 10)
   358  		if overflow {
   359  			return nil, errors.New("invalid withdraw threshold")
   360  		}
   361  	}
   362  	return &AssetDetailsErc20{
   363  		ERC20: &ERC20{
   364  			ChainID:           p.Erc20.ChainId,
   365  			ContractAddress:   crypto.EthereumChecksumAddress(p.Erc20.ContractAddress),
   366  			LifetimeLimit:     lifetimeLimit,
   367  			WithdrawThreshold: withdrawThreshold,
   368  		},
   369  	}, nil
   370  }
   371  
   372  // An ERC20 token based asset, living on the ethereum network.
   373  type ERC20 struct {
   374  	// Chain ID from which the asset originated from.
   375  	ChainID string
   376  
   377  	ContractAddress   string
   378  	LifetimeLimit     *num.Uint
   379  	WithdrawThreshold *num.Uint
   380  }
   381  
   382  func (e ERC20) DeepClone() *ERC20 {
   383  	cpy := &ERC20{
   384  		ContractAddress: e.ContractAddress,
   385  		ChainID:         e.ChainID,
   386  	}
   387  	if e.LifetimeLimit != nil {
   388  		cpy.LifetimeLimit = e.LifetimeLimit.Clone()
   389  	} else {
   390  		cpy.LifetimeLimit = num.UintZero()
   391  	}
   392  	if e.WithdrawThreshold != nil {
   393  		cpy.WithdrawThreshold = e.WithdrawThreshold.Clone()
   394  	} else {
   395  		cpy.WithdrawThreshold = num.UintZero()
   396  	}
   397  	return cpy
   398  }
   399  
   400  func (e ERC20) String() string {
   401  	return fmt.Sprintf(
   402  		"contractAddress(%s) chainID(%s) lifetimeLimit(%s) withdrawThreshold(%s)",
   403  		e.ContractAddress,
   404  		e.ChainID,
   405  		stringer.PtrToString(e.LifetimeLimit),
   406  		stringer.PtrToString(e.WithdrawThreshold),
   407  	)
   408  }