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); }
/// <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)); }
/// <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)); }
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() } }); } }
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() } }); } }
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)); }
/// <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 })); }
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)); }
/// <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); } }
/// <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); }
private Task RecordSuccessfulFaucetDripAsync(INetworkAccount recipient, EthereumAmount nativeCurrencyAmount, Token tokenAmount, IPAddress ipAddress) { return(this._faucetDataManager.RecordFundsIssuedAsync(recipient: recipient, nativeCurrencyAmount: nativeCurrencyAmount, tokenAmount: tokenAmount, ipAddress: ipAddress)); }
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); }
private static string GetContractTokenBalanceAlertKey(INetworkAccount account) { return(BuildKey(context: @"Balance", network: account.Network, account.Address.ToString(), @"TOKEN")); }
/// <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 })); }