code.vegaprotocol.io/vega@v0.79.0/core/banking/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 banking 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "math/big" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/assets/erc20" 26 "code.vegaprotocol.io/vega/core/events" 27 "code.vegaprotocol.io/vega/core/types" 28 "code.vegaprotocol.io/vega/libs/num" 29 "code.vegaprotocol.io/vega/logging" 30 ) 31 32 var ( 33 ErrInvalidWithdrawalReferenceNonce = errors.New("invalid withdrawal reference nonce") 34 ErrWithdrawalAmountUnderMinimalRequired = errors.New("invalid withdrawal, amount under minimum required") 35 ErrAssetAlreadyBeingListed = errors.New("asset already being listed") 36 ErrWithdrawalDisabledWhenBridgeIsStopped = errors.New("cannot issue withdrawals when the bridge is stopped") 37 ) 38 39 type ERC20BridgeView interface { 40 FindAssetList(al *types.ERC20AssetList, blockNumber, logIndex uint64, txHash string) error 41 FindBridgeStopped(al *types.ERC20EventBridgeStopped, blockNumber, logIndex uint64, txHash string) error 42 FindBridgeResumed(al *types.ERC20EventBridgeResumed, blockNumber, logIndex uint64, txHash string) error 43 FindDeposit(d *types.ERC20Deposit, blockNumber, logIndex uint64, ethAssetAddress string, txHash string) error 44 FindAssetLimitsUpdated(update *types.ERC20AssetLimitsUpdated, blockNumber uint64, logIndex uint64, ethAssetAddress string, txHash string) error 45 CollateralBridgeAddress() string 46 } 47 48 func (e *Engine) EnableERC20( 49 _ context.Context, 50 al *types.ERC20AssetList, 51 id string, 52 blockNumber, txIndex uint64, 53 txHash, chainID string, 54 ) error { 55 asset, _ := e.assets.Get(al.VegaAssetID) 56 if _, ok := e.assetActions[al.VegaAssetID]; ok { 57 e.log.Error("asset already being listed", logging.AssetID(al.VegaAssetID)) 58 return ErrAssetAlreadyBeingListed 59 } 60 61 bridgeView, err := e.bridgeViewForChainID(chainID) 62 if err != nil { 63 return err 64 } 65 66 aa := &assetAction{ 67 id: id, 68 state: newPendingState(), 69 erc20AL: al, 70 asset: asset, 71 blockHeight: blockNumber, 72 logIndex: txIndex, 73 txHash: txHash, 74 chainID: chainID, 75 bridgeView: bridgeView, 76 } 77 e.addAction(aa) 78 return e.witness.StartCheck(aa, e.onCheckDone, e.timeService.GetTimeNow().Add(defaultValidationDuration)) 79 } 80 81 func (e *Engine) UpdateERC20( 82 _ context.Context, 83 event *types.ERC20AssetLimitsUpdated, 84 id string, 85 blockNumber, txIndex uint64, 86 txHash, chainID string, 87 ) error { 88 asset, err := e.assets.Get(event.VegaAssetID) 89 if err != nil { 90 e.log.Panic("couldn't retrieve the ERC20 asset", 91 logging.AssetID(event.VegaAssetID), 92 ) 93 } 94 95 bridgeView, err := e.bridgeViewForChainID(chainID) 96 if err != nil { 97 return err 98 } 99 100 aa := &assetAction{ 101 id: id, 102 state: newPendingState(), 103 erc20AssetLimitsUpdated: event, 104 asset: asset, 105 blockHeight: blockNumber, 106 logIndex: txIndex, 107 txHash: txHash, 108 chainID: chainID, 109 bridgeView: bridgeView, 110 } 111 e.addAction(aa) 112 return e.witness.StartCheck(aa, e.onCheckDone, e.timeService.GetTimeNow().Add(defaultValidationDuration)) 113 } 114 115 func (e *Engine) DepositERC20( 116 ctx context.Context, 117 d *types.ERC20Deposit, 118 id string, 119 blockNumber, logIndex uint64, 120 txHash, chainID string, 121 ) error { 122 dep := e.newDeposit(id, d.TargetPartyID, d.VegaAssetID, d.Amount, txHash) 123 124 // check if the asset is correct 125 asset, err := e.assets.Get(d.VegaAssetID) 126 if err != nil { 127 dep.Status = types.DepositStatusCancelled 128 e.broker.Send(events.NewDepositEvent(ctx, *dep)) 129 e.log.Error("unable to get asset by id", 130 logging.AssetID(d.VegaAssetID), 131 logging.Error(err)) 132 return err 133 } 134 135 if !asset.IsERC20() { 136 dep.Status = types.DepositStatusCancelled 137 e.broker.Send(events.NewDepositEvent(ctx, *dep)) 138 return fmt.Errorf("%v: %w", asset.String(), ErrWrongAssetTypeUsedInERC20ChainEvent) 139 } 140 141 bridgeView, err := e.bridgeViewForChainID(chainID) 142 if err != nil { 143 return err 144 } 145 146 aa := &assetAction{ 147 id: dep.ID, 148 state: newPendingState(), 149 erc20D: d, 150 asset: asset, 151 blockHeight: blockNumber, 152 logIndex: logIndex, 153 txHash: txHash, 154 chainID: chainID, 155 bridgeView: bridgeView, 156 } 157 e.addAction(aa) 158 e.deposits[dep.ID] = dep 159 160 e.broker.Send(events.NewDepositEvent(ctx, *dep)) 161 return e.witness.StartCheck(aa, e.onCheckDone, e.timeService.GetTimeNow().Add(defaultValidationDuration)) 162 } 163 164 func (e *Engine) ERC20WithdrawalEvent(ctx context.Context, w *types.ERC20Withdrawal, blockNumber uint64, txHash string, chainID string) error { 165 // check straight away if the withdrawal is signed 166 nonce, ok := new(big.Int).SetString(w.ReferenceNonce, 10) 167 if !ok { 168 return fmt.Errorf("%s: %w", w.ReferenceNonce, ErrInvalidWithdrawalReferenceNonce) 169 } 170 171 withd, err := e.getWithdrawalFromRef(nonce) 172 if err != nil { 173 return fmt.Errorf("%s: %w", w.ReferenceNonce, err) 174 } 175 if withd.Status != types.WithdrawalStatusFinalized { 176 return fmt.Errorf("%s: %w", withd.ID, ErrInvalidWithdrawalState) 177 } 178 if _, ok := e.notary.IsSigned(ctx, withd.ID, types.NodeSignatureKindAssetWithdrawal); !ok { 179 return ErrWithdrawalNotReady 180 } 181 182 if e.primaryEthChainID == chainID && blockNumber > e.lastSeenPrimaryEthBlock { 183 e.lastSeenPrimaryEthBlock = blockNumber 184 } else if e.secondaryEthChainID == chainID && blockNumber > e.lastSeenSecondaryEthBlock { 185 e.lastSeenSecondaryEthBlock = blockNumber 186 } 187 withd.WithdrawalDate = e.timeService.GetTimeNow().UnixNano() 188 withd.TxHash = txHash 189 e.broker.Send(events.NewWithdrawalEvent(ctx, *withd)) 190 191 return nil 192 } 193 194 func (e *Engine) WithdrawERC20( 195 ctx context.Context, 196 id, party, assetID string, 197 amount *num.Uint, 198 ext *types.Erc20WithdrawExt, 199 ) error { 200 asset, err := e.assets.Get(assetID) 201 if err != nil { 202 e.log.Debug("unable to get asset by id", 203 logging.AssetID(assetID), 204 logging.Error(err)) 205 return err 206 } 207 208 if !asset.IsERC20() { 209 return fmt.Errorf("asset %q is not an ERC20 token: %w", assetID, err) 210 } 211 212 erc20Token, _ := asset.ERC20() 213 bridgeState, err := e.bridgeStateForChainID(erc20Token.ChainID()) 214 if err != nil { 215 return err 216 } 217 218 if bridgeState.IsStopped() { 219 return ErrWithdrawalDisabledWhenBridgeIsStopped 220 } 221 222 wext := &types.WithdrawExt{ 223 Ext: &types.WithdrawExtErc20{ 224 Erc20: ext, 225 }, 226 } 227 228 w, ref := e.newWithdrawal(id, party, assetID, amount, wext) 229 230 e.broker.Send(events.NewWithdrawalEvent(ctx, *w)) 231 e.withdrawals[w.ID] = withdrawalRef{w: w, ref: ref} 232 233 // check for minimal amount reached 234 quantum := asset.Type().Details.Quantum 235 // no reason this would produce an error 236 minAmount, _ := num.UintFromDecimal(quantum.Mul(e.minWithdrawQuantumMultiple)) 237 238 // now verify amount 239 if amount.LT(minAmount) { 240 e.log.Debug("cannot withdraw funds, the request is less than minimum withdrawal amount", 241 logging.BigUint("min-amount", minAmount), 242 logging.BigUint("requested-amount", amount), 243 ) 244 w.Status = types.WithdrawalStatusRejected 245 e.broker.Send(events.NewWithdrawalEvent(ctx, *w)) 246 return ErrWithdrawalAmountUnderMinimalRequired 247 } 248 249 if a, ok := asset.ERC20(); !ok { 250 w.Status = types.WithdrawalStatusRejected 251 e.broker.Send(events.NewWithdrawalEvent(ctx, *w)) 252 return ErrWrongAssetUsedForERC20Withdraw 253 } else if threshold := a.Type().Details.GetERC20().WithdrawThreshold; threshold != nil && threshold.NEQ(num.UintZero()) { 254 // a delay will be applied on this withdrawal 255 if threshold.LT(amount) { 256 e.log.Debug("withdraw threshold breached, delay will be applied", 257 logging.PartyID(party), 258 logging.BigUint("threshold", threshold), 259 logging.BigUint("amount", amount), 260 logging.AssetID(assetID), 261 logging.Error(err)) 262 } 263 } 264 265 // try to withdraw if no error, this'll just abort 266 if err := e.finalizeWithdraw(ctx, w); err != nil { 267 return err 268 } 269 270 // no check error as we checked earlier we had an erc20 asset. 271 erc20asset, _ := asset.ERC20() 272 273 // startup aggregating signature for the bundle 274 return e.startERC20Signatures(w, erc20asset, ref) 275 } 276 277 func (e *Engine) startERC20Signatures(w *types.Withdrawal, asset *erc20.ERC20, ref *big.Int) error { 278 var ( 279 signature []byte 280 err error 281 ) 282 283 creation := time.Unix(0, w.CreationDate) 284 // if we are a validator, we want to build a signature 285 if e.top.IsValidator() { 286 _, signature, err = asset.SignWithdrawal( 287 w.Amount, w.Ext.GetErc20().GetReceiverAddress(), ref, creation) 288 if err != nil { 289 // there's no reason we cannot build the signature here 290 // apart if the node isn't configure properly 291 e.log.Panic("unable to sign withdrawal", 292 logging.WithdrawalID(w.ID), 293 logging.PartyID(w.PartyID), 294 logging.AssetID(w.Asset), 295 logging.BigUint("amount", w.Amount), 296 logging.Error(err)) 297 } 298 } 299 300 // we were able to lock the funds, then we can send the vote through the network 301 e.notary.StartAggregate(w.ID, types.NodeSignatureKindAssetWithdrawal, signature) 302 303 return nil 304 } 305 306 func (e *Engine) offerERC20NotarySignatures(resource string) []byte { 307 if !e.top.IsValidator() { 308 return nil 309 } 310 311 wref, ok := e.withdrawals[resource] 312 if !ok { 313 // there's no reason we cannot find the withdrawal here 314 // apart if the node isn't configured properly 315 e.log.Panic("unable to find withdrawal", 316 logging.WithdrawalID(resource)) 317 } 318 w := wref.w 319 320 asset, err := e.assets.Get(w.Asset) 321 if err != nil { 322 // there's no reason we cannot build the signature here 323 // apart if the node isn't configure properly 324 e.log.Panic("unable to get asset when offering signature", 325 logging.WithdrawalID(w.ID), 326 logging.PartyID(w.PartyID), 327 logging.AssetID(w.Asset), 328 logging.BigUint("amount", w.Amount), 329 logging.Error(err)) 330 } 331 332 creation := time.Unix(0, w.CreationDate) 333 erc20asset, _ := asset.ERC20() 334 _, signature, err := erc20asset.SignWithdrawal( 335 w.Amount, w.Ext.GetErc20().GetReceiverAddress(), wref.ref, creation) 336 if err != nil { 337 // there's no reason we cannot build the signature here 338 // apart if the node isn't configure properly 339 e.log.Panic("unable to sign withdrawal", 340 logging.WithdrawalID(w.ID), 341 logging.PartyID(w.PartyID), 342 logging.AssetID(w.Asset), 343 logging.BigUint("amount", w.Amount), 344 logging.Error(err)) 345 } 346 347 return signature 348 } 349 350 func (e *Engine) addAction(aa *assetAction) { 351 e.assetActions[aa.id] = aa 352 }