code.vegaprotocol.io/vega@v0.79.0/core/assets/erc20/erc20.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 erc20
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"math/big"
    23  	"strings"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/assets/common"
    27  	"code.vegaprotocol.io/vega/core/bridges"
    28  	ethnw "code.vegaprotocol.io/vega/core/nodewallets/eth"
    29  	"code.vegaprotocol.io/vega/core/types"
    30  	"code.vegaprotocol.io/vega/libs/num"
    31  	typespb "code.vegaprotocol.io/vega/protos/vega"
    32  
    33  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    34  	ethcommon "github.com/ethereum/go-ethereum/common"
    35  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    36  )
    37  
    38  var ErrNotAnErc20Asset = errors.New("not an erc20 asset")
    39  
    40  //go:generate go run github.com/golang/mock/mockgen -destination mocks/eth_client_mock.go -package mocks code.vegaprotocol.io/vega/core/assets/erc20 ETHClient
    41  type ETHClient interface {
    42  	bind.ContractBackend
    43  	HeaderByNumber(context.Context, *big.Int) (*ethtypes.Header, error)
    44  	CollateralBridgeAddress() ethcommon.Address
    45  	CurrentHeight(context.Context) (uint64, error)
    46  	ConfirmationsRequired() uint64
    47  	ChainID(ctx context.Context) (*big.Int, error)
    48  	IsEthereum() bool
    49  }
    50  
    51  type ERC20 struct {
    52  	asset     *types.Asset
    53  	address   string
    54  	chainID   string
    55  	ok        bool
    56  	wallet    ethnw.EthereumWallet
    57  	ethClient ETHClient
    58  }
    59  
    60  func New(
    61  	id string,
    62  	asset *types.AssetDetails,
    63  	w ethnw.EthereumWallet,
    64  	ethClient ETHClient,
    65  ) (*ERC20, error) {
    66  	source := asset.GetERC20()
    67  	if source == nil {
    68  		return nil, ErrNotAnErc20Asset
    69  	}
    70  
    71  	return &ERC20{
    72  		asset: &types.Asset{
    73  			ID:      id,
    74  			Details: asset,
    75  			Status:  types.AssetStatusProposed,
    76  		},
    77  		chainID:   source.ChainID,
    78  		address:   source.ContractAddress,
    79  		wallet:    w,
    80  		ethClient: ethClient,
    81  	}, nil
    82  }
    83  
    84  func (e *ERC20) SetPendingListing() {
    85  	e.asset.Status = types.AssetStatusPendingListing
    86  }
    87  
    88  func (e *ERC20) SetRejected() {
    89  	e.asset.Status = types.AssetStatusRejected
    90  }
    91  
    92  func (e *ERC20) SetEnabled() {
    93  	e.asset.Status = types.AssetStatusEnabled
    94  }
    95  
    96  // SetChainID sets the chain-id on the ERC20. This should only be used during the migration from 0.75.
    97  func (e *ERC20) SetChainID(chainID string) {
    98  	e.chainID = chainID
    99  	source := e.asset.Details.Source.(*types.AssetDetailsErc20)
   100  	source.ERC20.ChainID = chainID
   101  }
   102  
   103  func (e *ERC20) Update(updatedAsset *types.Asset) {
   104  	e.asset = updatedAsset
   105  }
   106  
   107  func (e *ERC20) Address() string {
   108  	return e.address
   109  }
   110  
   111  func (e *ERC20) ProtoAsset() *typespb.Asset {
   112  	return e.asset.IntoProto()
   113  }
   114  
   115  func (e ERC20) Type() *types.Asset {
   116  	return e.asset.DeepClone()
   117  }
   118  
   119  func (e ERC20) ChainID() string {
   120  	return e.chainID
   121  }
   122  
   123  func (e *ERC20) GetAssetClass() common.AssetClass {
   124  	return common.ERC20
   125  }
   126  
   127  func (e *ERC20) IsValid() bool {
   128  	return e.ok
   129  }
   130  
   131  func (e *ERC20) SetValid() {
   132  	e.ok = true
   133  }
   134  
   135  // SignListAsset create and sign the message to
   136  // be sent to the bridge to whitelist the asset
   137  // return the generated message and the signature for this message.
   138  func (e *ERC20) SignListAsset() (msg []byte, sig []byte, err error) {
   139  	bridgeAddress := e.ethClient.CollateralBridgeAddress().Hex()
   140  	// use the asset ID converted into a uint256
   141  	// trim left all 0 as these makes for an invalid base16 numbers
   142  	nonce, err := num.UintFromHex("0x" + strings.TrimLeft(e.asset.ID, "0"))
   143  	if err != nil {
   144  		return nil, nil, err
   145  	}
   146  
   147  	source := e.asset.Details.GetERC20()
   148  	bundle, err := bridges.NewERC20Logic(e.wallet, bridgeAddress, e.chainID, false).
   149  		ListAsset(e.address, e.asset.ID, source.LifetimeLimit, source.WithdrawThreshold, nonce)
   150  	if err != nil {
   151  		return nil, nil, err
   152  	}
   153  
   154  	return bundle.Message, bundle.Signature, nil
   155  }
   156  
   157  func (e *ERC20) SignSetAssetLimits(nonce *num.Uint, lifetimeLimit *num.Uint, withdrawThreshold *num.Uint) (msg []byte, sig []byte, err error) {
   158  	bridgeAddress := e.ethClient.CollateralBridgeAddress().Hex()
   159  	bundle, err := bridges.NewERC20Logic(e.wallet, bridgeAddress, e.chainID, false).
   160  		SetAssetLimits(e.address, lifetimeLimit, withdrawThreshold, nonce)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  
   165  	return bundle.Message, bundle.Signature, nil
   166  }
   167  
   168  func (e *ERC20) SignWithdrawal(
   169  	amount *num.Uint,
   170  	ethPartyAddress string,
   171  	withdrawRef *big.Int,
   172  	now time.Time,
   173  ) (msg []byte, sig []byte, err error) {
   174  	nonce, _ := num.UintFromBig(withdrawRef)
   175  	bridgeAddress := e.ethClient.CollateralBridgeAddress().Hex()
   176  	bundle, err := bridges.NewERC20Logic(e.wallet, bridgeAddress, e.chainID, false).
   177  		WithdrawAsset(e.address, amount, ethPartyAddress, now, nonce)
   178  	if err != nil {
   179  		return nil, nil, err
   180  	}
   181  
   182  	return bundle.Message, bundle.Signature, nil
   183  }
   184  
   185  func (e *ERC20) String() string {
   186  	return fmt.Sprintf("id(%v) name(%v) symbol(%v) decimals(%v)",
   187  		e.asset.ID, e.asset.Details.Name, e.asset.Details.Symbol, e.asset.Details.Decimals)
   188  }