Esempio n. 1
0
        private async Task <(EthereumAmount sourceAccountBalance, EthereumAmount recipientEthBalance)> GetNativeCurrencyBalancesAsync(
            INetworkAccount recipient,
            ContractAddress faucetContractAddress,
            INetworkBlockHeader networkBlockHeader,
            CancellationToken cancellationToken)
        {
            IReadOnlyDictionary <EthereumAddress, EthereumAmount> accountBalances = await this._ethereumAccountBalanceSource.GetAccountBalancesAsync(
                networkAccounts : new EthereumAddress[] { faucetContractAddress, recipient.Address },
                networkBlockHeader : networkBlockHeader,
                cancellationToken : cancellationToken);

            // get the SOURCE account's balance
            if (!accountBalances.TryGetValue(key: faucetContractAddress, out EthereumAmount? sourceAccountBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {recipient.Network.NativeCurrency} balance for {faucetContractAddress}");

                throw new InsufficientTokenException();
            }

            if (!accountBalances.TryGetValue(key: recipient.Address, out EthereumAmount? recipientEthBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {recipient.Network.NativeCurrency} balance for {recipient.Address}");

                throw new InsufficientTokenException();
            }

            return(sourceAccountBalance, recipientEthBalance);
        }
Esempio n. 2
0
        /// <inheritdoc />
        public Task SaveReplacementAsync(INetworkAccount account,
                                         NewTransaction transaction,
                                         TransactionHash replacesTransactionHash,
                                         TransactionReplacementReason reasonForTransactionReplacement,
                                         TransactionState chainStatusOfPreviousTransaction)
        {
            var parameters = new
            {
                Network = account.Network.Name,
                Account = account.Address,
                transaction.ContractAddress,
                transaction.FunctionName,
                TransactionData = transaction.Data,
                transaction.TransactionHash,
                transaction.Value,
                transaction.GasPrice,
                transaction.GasLimit,
                transaction.EstimatedGas,
                transaction.Nonce,
                ReplacesTransactionHash          = replacesTransactionHash,
                ReasonForSubmission              = reasonForTransactionReplacement.GetName(),
                ChainStatusOfPreviousTransaction = chainStatusOfPreviousTransaction.GetName(),
                Priority = transaction.Priority.GetName()
            };

            return(this._database.ExecuteAsync(storedProcedure: @"Ethereum.Transaction_InsertReplacement", param: parameters));
        }
Esempio n. 3
0
        /// <inheritdoc />
        public async Task <FaucetDrip> OpenAsync(IPAddress ipAddress, INetworkAccount recipient, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken)
        {
            const string sourceName = @"Test Network Faucet";

            if (!this._faucetContract.Addresses.TryGetValue(key: recipient.Network, out ContractAddress? faucetContractAddress))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Cannot issue {recipient.Network.NativeCurrency} from faucet. Faucet not available on network");

                throw new InsufficientTokenException();
            }

            INetworkSigningAccount networkSigningAccount = this._ethereumAccountManager.GetAccount(network: recipient.Network);

            (EthereumAmount sourceAccountBalance, EthereumAmount recipientEthBalance) = await this.GetNativeCurrencyBalancesAsync(
                recipient : recipient,
                faucetContractAddress : faucetContractAddress,
                networkBlockHeader : networkBlockHeader,
                cancellationToken : cancellationToken);

            (Token sourceBalanceForToken, Token recipientBalanceForToken) =
                await this.GetTokenBalancesAsync(recipient : recipient, networkBlockHeader : networkBlockHeader, faucetContractAddress : faucetContractAddress, cancellationToken : cancellationToken);

            (EthereumAmount nativeCurrencyAmount, Token tokenAmount) = this.CalculateFundsToIssue(recipient: recipient,
                                                                                                  recipientNativeCurrencyBalance: recipientEthBalance,
                                                                                                  recipientTokenBalance: recipientBalanceForToken,
                                                                                                  faucetNativeCurrencyBalance: sourceAccountBalance,
                                                                                                  faucetTokenBalance: sourceBalanceForToken);

            if (!await this.IsAllowedToIssueFromFaucetAsync(ipAddress: ipAddress, recipientAddress: recipient.Address))
            {
                throw new TooFrequentTokenException();
            }

            Issuance issuance =
                new(recipient : recipient, ethToIssue : nativeCurrencyAmount, tokenToIssue : tokenAmount, sourceAccountBalance : sourceAccountBalance, sourceTokenBalance : sourceBalanceForToken, sourceName
                    : sourceName, new NetworkContract(network : recipient.Network, contractAddress : faucetContractAddress), contractInfo : this._faucetContract);

            try
            {
                PendingTransaction tx = await this.IssueFundsAsync(networkSigningAccount : networkSigningAccount,
                                                                   issuance : issuance,
                                                                   new TransactionContext(contextType : WellKnownContracts.Faucet, recipient.Address.ToString()));

                await this.RecordSuccessfulFaucetDripAsync(recipient : recipient, nativeCurrencyAmount : nativeCurrencyAmount, tokenAmount : tokenAmount, ipAddress : ipAddress);

                return(new FaucetDrip(transaction: tx, ethAmount: nativeCurrencyAmount, tokenAmount: tokenAmount));
            }
            catch (TransactionWillAlwaysFailException exception)
            {
                this._logger.LogCritical(new EventId(exception.HResult),
                                         exception: exception,
                                         $"{issuance.Recipient.Network.Name}: Cannot issue {this._tokenContract.Symbol} from faucet: {exception.Message}");

                throw new InsufficientTokenException(message: "Could not request funds from faucet", innerException: exception);
            }
        }
 /// <inheritdoc />
 public Task RecordFundsIssuedAsync(INetworkAccount recipient, EthereumAmount nativeCurrencyAmount, Token tokenAmount, IPAddress ipAddress)
 {
     return(this._database.ExecuteAsync(storedProcedure: @"Faucet.Minting_Success",
                                        new
     {
         Network = recipient.Network.Name,
         recipient.Address,
         IPAddress = ipAddress,
         NativeCurrencyIssued = nativeCurrencyAmount,
         TokenIssued = tokenAmount
     }));
 }
        /// <inheritdoc />
        public async Task <EthereumAccountNonce> GetNextNonceAsync(INetworkAccount account)
        {
            AccountNonceEntity?accountNonceEntity = await this._database.QuerySingleOrDefaultAsync <object, AccountNonceEntity>(
                storedProcedure : @"Ethereum.AccountNonce_Get",
                new { Account = account.Address, Network = account.Network.Name, MachineName = this._machineName });

            if (accountNonceEntity?.Nonce == null)
            {
                throw new NonceNotAvailableForAccountException($"Could not retrieve nonce for {account.Address} on {account.Network.Name}");
            }

            return(new EthereumAccountNonce(account: account, nonce: accountNonceEntity.Nonce));
        }
Esempio n. 6
0
 private async Task NotifyForAllTokenBalanceChangesAsync(INetworkAccount contractAccount, TokenAmount minimumTokenAmount, TokenBalanceChangeEventArgs args)
 {
     if (args.NewBalance.TokenAmount < minimumTokenAmount)
     {
         string message = $"{contractAccount.Network.Name}: Faucet contract at address {contractAccount.Address} is low on {args.Token.Symbol}";
         this._logger.LogCritical(message);
         await this._alertDispatcher.TriggerAsync(GetContractTokenBalanceAlertKey(args.Account),
                                                  summary : message,
                                                  severity : EventSeverity.CRITICAL,
                                                  new Dictionary <string, string> {
             { "Current balance", args.NewBalance.ToFormattedUnit() }
         });
     }
 }
Esempio n. 7
0
 private async Task NotifyForAllEthBalanceChangesAsync(INetworkAccount contractAccount, EthereumAmount minimumEthereumAmount, EthereumBalanceChangeEventArgs args)
 {
     if (args.NewBalance < minimumEthereumAmount)
     {
         string message = $"{contractAccount.Network.Name}: Faucet contract at address {contractAccount.Address}  is low on XDAI";
         this._logger.LogCritical(message);
         await this._alertDispatcher.TriggerAsync(GetContractEthBalanceAlertKey(args.Account),
                                                  summary : message,
                                                  severity : EventSeverity.CRITICAL,
                                                  new Dictionary <string, string> {
             { "Current balance", args.NewBalance.Value.ToString() }
         });
     }
 }
Esempio n. 8
0
 public Issuance(INetworkAccount recipient,
                 EthereumAmount ethToIssue,
                 Token tokenToIssue,
                 EthereumAmount sourceAccountBalance,
                 Token sourceTokenBalance,
                 string sourceName,
                 NetworkContract fundsSourceContract,
                 IContractInfo contractInfo)
 {
     this.Recipient            = recipient ?? throw new ArgumentNullException(nameof(recipient));
     this.EthToIssue           = ethToIssue ?? throw new ArgumentNullException(nameof(ethToIssue));
     this.TokenToIssue         = tokenToIssue ?? throw new ArgumentNullException(nameof(tokenToIssue));
     this.SourceAccountBalance = sourceAccountBalance ?? throw new ArgumentNullException(nameof(sourceAccountBalance));
     this.SourceTokenBalance   = sourceTokenBalance ?? throw new ArgumentNullException(nameof(sourceTokenBalance));
     this.SourceName           = sourceName ?? throw new ArgumentNullException(nameof(sourceName));
     this.FundsSourceContract  = fundsSourceContract ?? throw new ArgumentNullException(nameof(fundsSourceContract));
     this._contractInfo        = contractInfo ?? throw new ArgumentNullException(nameof(contractInfo));
 }
Esempio n. 9
0
        /// <inheritdoc />
        public Task SaveNewAsync(INetworkAccount account, NewTransaction transaction, TransactionContext?context)
        {
            var parameters = new
            {
                Network = account.Network.Name,
                Account = account.Address,
                transaction.ContractAddress,
                transaction.FunctionName,
                TransactionData = transaction.Data,
                transaction.TransactionHash,
                transaction.Value,
                transaction.GasPrice,
                transaction.GasLimit,
                transaction.EstimatedGas,
                transaction.Nonce,
                context?.ContextType,
                context?.ContextId,
                Priority = transaction.Priority.GetName()
            };

            return(this._database.ExecuteAsync(storedProcedure: @"Ethereum.Transaction_Insert", param: parameters));
        }
 /// <inheritdoc />
 public Task <TransactionSubmissionStatus?> GetStatusAsync(INetworkAccount networkAccount)
 {
     return(this._database.QuerySingleOrDefaultAsync(builder: this._transactionSubmissionStatusBuilder,
                                                     storedProcedure: @"Ethereum.TransactionStatus_Get",
                                                     new { network = networkAccount.Network.Name, accountAddress = networkAccount.Address }));
 }
Esempio n. 11
0
        private async Task <(Token sourceBalanceForToken, Token recipientBalanceForToken)> GetTokenBalancesAsync(INetworkAccount recipient,
                                                                                                                 INetworkBlockHeader networkBlockHeader,
                                                                                                                 ContractAddress faucetContractAddress,
                                                                                                                 CancellationToken cancellationToken)
        {
            IReadOnlyDictionary <EthereumAddress, Erc20TokenBalance> balances = await this._ethereumAccountBalanceSource.GetErc20TokenBalancesAsync(
                addresses : new EthereumAddress[] { faucetContractAddress, recipient.Address },
                networkBlockHeader : networkBlockHeader,
                tokenContract : this._tokenContract,
                cancellationToken : cancellationToken);

            if (!balances.TryGetValue(key: faucetContractAddress, out Erc20TokenBalance sourceTokenBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {this._tokenContract.Symbol} balance for {faucetContractAddress}");

                throw new InsufficientTokenException();
            }

            if (!balances.TryGetValue(key: recipient.Address, out Erc20TokenBalance recipientTokenBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {this._tokenContract.Symbol} balance for {recipient.Address}");

                throw new InsufficientTokenException();
            }

            return(sourceBalanceForToken : new Token(sourceTokenBalance), recipientBalanceForToken : new Token(recipientTokenBalance));
        }
Esempio n. 12
0
        /// <inheritdoc />
        public Task <PendingTransaction?> GetTransactionByHashAsync(INetworkAccount account, TransactionHash transactionHash)
        {
            var parameters = new { Network = account.Network.Name, Account = account.Address, TransactionHash = transactionHash };

            return(this._database.QuerySingleOrDefaultAsync(builder: this._pendingTransactionBuilder, storedProcedure: @"Ethereum.Transaction_GetByTransactionHash", param: parameters));
        }
        /// <inheritdoc />
        public async Task <FaucetDrip> OpenAsync(IPAddress ipAddress, INetworkAccount recipient, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken)
        {
            const string sourceName = @"Test Network Faucet";

            // ETH & FUN source (Faucet account - no need to be a signing account)
            if (!this._faucetContract.Addresses.TryGetValue(key: recipient.Network, out ContractAddress? contractAddress))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Cannot issue ETH from faucet. Faucet not available on network");

                throw new InsufficientTokenException();
            }

            INetworkSigningAccount networkSigningAccount = this._ethereumAccountManager.GetAccount(network: recipient.Network);

            NetworkContract fundsSourceContract = new(network : recipient.Network, contractAddress : contractAddress);

            IReadOnlyList <EthereumAddress> networkAccounts = new EthereumAddress[] { contractAddress, recipient.Address };

            IReadOnlyDictionary <EthereumAddress, EthereumAmount> accountBalances =
                await this._ethereumAccountBalanceSource.GetAccountBalancesAsync(networkAccounts : networkAccounts, networkBlockHeader : networkBlockHeader, cancellationToken : CancellationToken.None);

            // get the SOURCE account's balance
            if (!accountBalances.TryGetValue(key: contractAddress, out EthereumAmount? sourceAccountBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {networkSigningAccount.Address}");

                throw new InsufficientTokenException();
            }

            // get the account's current ETH balance
            if (!accountBalances.TryGetValue(key: recipient.Address, out EthereumAmount? recipientEthBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {recipient.Address}");

                throw new InsufficientTokenException();
            }

            IReadOnlyDictionary <EthereumAddress, Erc20TokenBalance> balances = await this._ethereumAccountBalanceSource.GetErc20TokenBalancesAsync(
                addresses : networkAccounts,
                networkBlockHeader : networkBlockHeader,
                tokenContract : this._tokenContract,
                cancellationToken : cancellationToken);

            // get the SOURCE account's current FUN balance
            if (!balances.TryGetValue(key: fundsSourceContract.Address, out Erc20TokenBalance sourceTokenBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {fundsSourceContract.Address}");

                throw new InsufficientTokenException();
            }

            Token sourceBalanceForToken = new(sourceTokenBalance);

            // get the recipient account's current FUN balance
            if (!balances.TryGetValue(key: recipient.Address, out Erc20TokenBalance recipientTokenBalance))
            {
                this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {recipient.Address}");

                throw new InsufficientTokenException();
            }

            Token recipientBalanceForToken = new(recipientTokenBalance);

            bool giveToken = recipientBalanceForToken < this._maximumRecipientTokenBalance;
            bool giveEth   = recipientEthBalance < this._maximumRecipientEthBalance;

            this._logger.LogInformation(
                $"{recipient.Network.Name}: Source: {sourceAccountBalance.ToFormattedUnitWithSymbol()} {sourceBalanceForToken.ToFormattedUnitWithSymbol()} Recipient: {recipientEthBalance.ToFormattedUnitWithSymbol()} {recipientBalanceForToken.ToFormattedUnitWithSymbol()} Issue: ETH: {giveEth} FUN: {giveToken} Max Eth: {this._maximumRecipientEthBalance.ToFormattedUnitWithSymbol()} Max FUN: {this._maximumRecipientTokenBalance.ToFormattedUnitWithSymbol()}");

            if (!giveToken && !giveEth)
            {
                if (this._executionEnvironment.IsDevelopmentOrTest())
                {
                    this._logger.LogWarning($"{recipient.Network.Name}: Could not issue eth to {recipient.Address} - Recipient balance > max");
                }

                throw new TooMuchTokenException();
            }

            if (!await this.IsAllowedToIssueFromFaucetAsync(ipAddress: ipAddress, recipientAddress: recipient.Address))
            {
                throw new TooFrequentTokenException();
            }

            EthereumAmount ethAmount   = giveEth ? this.CalculateAmountOfEthToIssueFromFaucet(recipientEthBalance) : EthereumAmount.Zero;
            Token          tokenAmount = giveToken ? this.CalculateAmountOfFunToIssueFromFaucet(recipientBalanceForToken) : Token.Zero;

            Issuance issuance =
                new(recipient : recipient, ethToIssue : ethAmount, tokenToIssue : tokenAmount, sourceAccountBalance : sourceAccountBalance, sourceTokenBalance : sourceBalanceForToken, sourceName :
                    sourceName, fundsSourceContract : fundsSourceContract, contractInfo : this._faucetContract);

            try
            {
                PendingTransaction tx = await this.IssueFundsAsync(networkSigningAccount : networkSigningAccount,
                                                                   issuance : issuance,
                                                                   new TransactionContext(contextType : WellKnownContracts.Faucet, recipient.Address.ToString()));

                return(new FaucetDrip(transaction: tx, ethAmount: ethAmount, tokenAmount: tokenAmount));
            }
            catch (TransactionWillAlwaysFailException exception)
            {
                this._logger.LogCritical(new EventId(exception.HResult), exception: exception, $"{issuance.Recipient.Network.Name}: Cannot issue FUN from faucet: {exception.Message}");

                throw new InsufficientTokenException(message: "Could not request fun from faucet", innerException: exception);
            }
        }
Esempio n. 14
0
        /// <inheritdoc />
        public async Task <IReadOnlyList <BrokenTransaction> > GetBrokenTransactionsAsync(INetworkAccount account, DateTime currentNetworkDateTime)
        {
            var parameters = new { Network = account.Network.Name, Account = account.Address, CurrentTimeOnNetwork = currentNetworkDateTime };

            IReadOnlyList <BrokenTransaction> entities = await this._database.QueryAsync(builder : this._brokenTransactionBuilder, storedProcedure : @"Ethereum.Transaction_GetBroken", param : parameters);

            return(entities.OrderBy(keySelector: x => x.Nonce)
                   .ToList());
        }
 /// <inheritdoc />
 public bool DoesAccountHaveEnoughBalance(INetworkAccount networkAccount)
 {
     return(this._houseAccounts.TryGetValue(new NetworkAccount(network: networkAccount.Network, address: networkAccount.Address), out bool isEnoughBalance) && isEnoughBalance);
 }
Esempio n. 16
0
 private Task RecordSuccessfulFaucetDripAsync(INetworkAccount recipient, EthereumAmount nativeCurrencyAmount, Token tokenAmount, IPAddress ipAddress)
 {
     return(this._faucetDataManager.RecordFundsIssuedAsync(recipient: recipient, nativeCurrencyAmount: nativeCurrencyAmount, tokenAmount: tokenAmount, ipAddress: ipAddress));
 }
Esempio n. 17
0
        private (EthereumAmount nativeCurrencyAmount, Token tokenAmount) CalculateFundsToIssue(INetworkAccount recipient,
                                                                                               EthereumAmount recipientNativeCurrencyBalance,
                                                                                               Token recipientTokenBalance,
                                                                                               EthereumAmount faucetNativeCurrencyBalance,
                                                                                               Token faucetTokenBalance)
        {
            bool giveNativeCurrency = recipientNativeCurrencyBalance < this._nativeCurrencyLimits.MaximumRecipientBalance;
            bool giveToken          = recipientTokenBalance < this._tokenCurrencyLimits.MaximumRecipientBalance;

            this._logger.LogInformation(
                $"{recipient.Network.Name}: Source: {faucetNativeCurrencyBalance.ToFormattedUnitWithSymbol()} {faucetTokenBalance.ToFormattedUnitWithSymbol()} Recipient: {recipientNativeCurrencyBalance.ToFormattedUnitWithSymbol()} {recipientTokenBalance.ToFormattedUnitWithSymbol()} Issue: ETH: {giveNativeCurrency} FUN: {giveToken} Max Eth: {this._nativeCurrencyLimits.MaximumRecipientBalance.ToFormattedUnitWithSymbol()} Max FUN: {this._tokenCurrencyLimits.MaximumRecipientBalance.ToFormattedUnitWithSymbol()}");

            if (!giveNativeCurrency && !giveToken)
            {
                if (this._executionEnvironment.IsDevelopmentOrTest())
                {
                    this._logger.LogWarning(
                        $"{recipient.Network.Name}: Could not issue {recipient.Network.NativeCurrency} or {this._tokenContract.Symbol} to {recipient.Address} - Recipient balance > max");
                }

                throw new TooMuchTokenException();
            }

            EthereumAmount nativeCurrencyAmount = giveNativeCurrency ? this.CalculateAmountOfNativeCurrencyToIssueFromFaucet(recipientNativeCurrencyBalance) : EthereumAmount.Zero;
            Token          tokenAmount          = giveToken ? this.CalculateAmountOfTokenToIssueFromFaucet(recipientTokenBalance) : Token.Zero;

            return(nativeCurrencyAmount, tokenAmount);
        }
Esempio n. 18
0
 private static string GetContractTokenBalanceAlertKey(INetworkAccount account)
 {
     return(BuildKey(context: @"Balance", network: account.Network, account.Address.ToString(), @"TOKEN"));
 }
Esempio n. 19
0
 /// <inheritdoc />
 public Task <IReadOnlyList <PendingTransaction> > GetPendingTransactionsAsync(INetworkAccount account)
 {
     return(this._database.QueryAsync(builder: this._pendingTransactionBuilder,
                                      storedProcedure: @"Ethereum.Transaction_GetPending",
                                      new { Network = account.Network.Name, AccountAddress = account.Address }));
 }