github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/swap/chequebook/init.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package chequebook 6 7 import ( 8 "context" 9 "crypto/rand" 10 "errors" 11 "fmt" 12 "math/big" 13 "time" 14 15 "github.com/ethereum/go-ethereum/common" 16 chaincfg "github.com/ethersphere/bee/v2/pkg/config" 17 "github.com/ethersphere/bee/v2/pkg/log" 18 "github.com/ethersphere/bee/v2/pkg/sctx" 19 "github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20" 20 "github.com/ethersphere/bee/v2/pkg/storage" 21 "github.com/ethersphere/bee/v2/pkg/transaction" 22 ) 23 24 const ( 25 chequebookKey = "swap_chequebook" 26 ChequebookDeploymentKey = "swap_chequebook_transaction_deployment" 27 28 balanceCheckBackoffDuration = 20 * time.Second 29 balanceCheckMaxRetries = 10 30 ) 31 32 const ( 33 erc20SmallUnitStr = "10000000000000000" 34 ethSmallUnitStr = "1000000000000000000" 35 ) 36 37 func checkBalance( 38 ctx context.Context, 39 logger log.Logger, 40 swapInitialDeposit *big.Int, 41 swapBackend transaction.Backend, 42 chainId int64, 43 overlayEthAddress common.Address, 44 erc20Token erc20.Service, 45 ) error { 46 timeoutCtx, cancel := context.WithTimeout(ctx, balanceCheckBackoffDuration*time.Duration(balanceCheckMaxRetries)) 47 defer cancel() 48 for { 49 erc20Balance, err := erc20Token.BalanceOf(timeoutCtx, overlayEthAddress) 50 if err != nil { 51 return err 52 } 53 54 ethBalance, err := swapBackend.BalanceAt(timeoutCtx, overlayEthAddress, nil) 55 if err != nil { 56 return err 57 } 58 59 gasPrice := sctx.GetGasPrice(ctx) 60 minimumEth := big.NewInt(0) 61 62 if gasPrice == nil { 63 gasPrice, err = swapBackend.SuggestGasPrice(timeoutCtx) 64 if err != nil { 65 return err 66 } 67 68 minimumEth = gasPrice.Mul(gasPrice, big.NewInt(250000)) 69 } 70 71 insufficientERC20 := erc20Balance.Cmp(swapInitialDeposit) < 0 72 insufficientETH := ethBalance.Cmp(minimumEth) < 0 73 74 erc20SmallUnit, ethSmallUnit := new(big.Int), new(big.Float) 75 erc20SmallUnit.SetString(erc20SmallUnitStr, 10) 76 ethSmallUnit.SetString(ethSmallUnitStr) 77 78 if insufficientERC20 || insufficientETH { 79 neededERC20, mod := new(big.Int).DivMod(swapInitialDeposit, erc20SmallUnit, new(big.Int)) 80 if mod.Cmp(big.NewInt(0)) > 0 { 81 // always round up the division as the bzzaar cannot handle decimals 82 neededERC20.Add(neededERC20, big.NewInt(1)) 83 } 84 85 neededETH := new(big.Float).Quo(new(big.Float).SetInt(minimumEth), ethSmallUnit) 86 87 ccfg, _ := chaincfg.GetByChainID(chainId) 88 swarmTokenName := ccfg.SwarmTokenSymbol 89 nativeTokenName := ccfg.NativeTokenSymbol 90 91 if insufficientETH && insufficientERC20 { 92 msg := fmt.Sprintf("cannot continue until there is at least min %s (for Gas) and at least min %s available on address", nativeTokenName, swarmTokenName) 93 logger.Warning(msg, "min_amount", neededETH, "min_bzz_amount", neededERC20, "address", overlayEthAddress) 94 } else if insufficientETH { 95 msg := fmt.Sprintf("cannot continue until there is at least min %s (for Gas) available on address", nativeTokenName) 96 logger.Warning(msg, "min_amount", neededETH, "address", overlayEthAddress) 97 } else { 98 msg := fmt.Sprintf("cannot continue until there is at least min %s available on address", swarmTokenName) 99 logger.Warning(msg, "min_amount", neededERC20, "address", overlayEthAddress) 100 } 101 if chainId == chaincfg.Testnet.ChainID { 102 logger.Warning("learn how to fund your node by visiting our docs at https://docs.ethswarm.org/docs/installation/fund-your-node") 103 } 104 select { 105 case <-time.After(balanceCheckBackoffDuration): 106 case <-timeoutCtx.Done(): 107 if insufficientERC20 { 108 return fmt.Errorf("insufficient %s for initial deposit", swarmTokenName) 109 } else { 110 return fmt.Errorf("insufficient %s for initial deposit", nativeTokenName) 111 } 112 } 113 continue 114 } 115 116 return nil 117 } 118 } 119 120 // Init initialises the chequebook service. 121 func Init( 122 ctx context.Context, 123 chequebookFactory Factory, 124 stateStore storage.StateStorer, 125 logger log.Logger, 126 swapInitialDeposit *big.Int, 127 transactionService transaction.Service, 128 swapBackend transaction.Backend, 129 chainId int64, 130 overlayEthAddress common.Address, 131 chequeSigner ChequeSigner, 132 erc20Service erc20.Service, 133 ) (chequebookService Service, err error) { 134 logger = logger.WithName(loggerName).Register() 135 136 var chequebookAddress common.Address 137 err = stateStore.Get(chequebookKey, &chequebookAddress) 138 if err != nil { 139 if !errors.Is(err, storage.ErrNotFound) { 140 return nil, err 141 } 142 143 var txHash common.Hash 144 err = stateStore.Get(ChequebookDeploymentKey, &txHash) 145 if err != nil && !errors.Is(err, storage.ErrNotFound) { 146 return nil, err 147 } 148 if errors.Is(err, storage.ErrNotFound) { 149 logger.Info("no chequebook found, deploying new one.") 150 err = checkBalance(ctx, logger, swapInitialDeposit, swapBackend, chainId, overlayEthAddress, erc20Service) 151 if err != nil { 152 return nil, err 153 } 154 155 nonce := make([]byte, 32) 156 _, err = rand.Read(nonce) 157 if err != nil { 158 return nil, err 159 } 160 161 // if we don't yet have a chequebook, deploy a new one 162 txHash, err = chequebookFactory.Deploy(ctx, overlayEthAddress, big.NewInt(0), common.BytesToHash(nonce)) 163 if err != nil { 164 return nil, err 165 } 166 167 logger.Info("deploying new chequebook", "tx", txHash) 168 169 err = stateStore.Put(ChequebookDeploymentKey, txHash) 170 if err != nil { 171 return nil, err 172 } 173 } else { 174 logger.Info("waiting for chequebook deployment", "tx", txHash) 175 } 176 177 chequebookAddress, err = chequebookFactory.WaitDeployed(ctx, txHash) 178 if err != nil { 179 return nil, err 180 } 181 182 logger.Info("chequebook deployed", "chequebook_address", chequebookAddress) 183 184 // save the address for later use 185 err = stateStore.Put(chequebookKey, chequebookAddress) 186 if err != nil { 187 return nil, err 188 } 189 190 chequebookService, err = New(transactionService, chequebookAddress, overlayEthAddress, stateStore, chequeSigner, erc20Service) 191 if err != nil { 192 return nil, err 193 } 194 195 if swapInitialDeposit.Cmp(big.NewInt(0)) != 0 { 196 logger.Info("depositing token into new chequebook", "amount", swapInitialDeposit) 197 depositHash, err := chequebookService.Deposit(ctx, swapInitialDeposit) 198 if err != nil { 199 return nil, err 200 } 201 202 logger.Info("sent deposit transaction", "tx", depositHash) 203 err = chequebookService.WaitForDeposit(ctx, depositHash) 204 if err != nil { 205 return nil, err 206 } 207 208 logger.Info("successfully deposited to chequebook") 209 } 210 } else { 211 chequebookService, err = New(transactionService, chequebookAddress, overlayEthAddress, stateStore, chequeSigner, erc20Service) 212 if err != nil { 213 return nil, err 214 } 215 216 logger.Info("using existing chequebook", "chequebook_address", chequebookAddress) 217 } 218 219 // regardless of how the chequebook service was initialised make sure that the chequebook is valid 220 err = chequebookFactory.VerifyChequebook(ctx, chequebookService.Address()) 221 if err != nil { 222 return nil, err 223 } 224 225 return chequebookService, nil 226 }