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 }