/// <summary> /// Checks if money were deposited to an address associated with any user who has a deposit address. /// When money are deposited user's balance is updated. /// </summary> private void CheckDeposits(BotDbContext context) { this.logger.Trace("()"); List <DiscordUserModel> usersToTrack = context.Users.Where(x => x.DepositAddress != null).ToList(); this.logger.Trace("Tracking {0} users.", usersToTrack.Count); foreach (DiscordUserModel user in usersToTrack) { decimal receivedByAddress = this.coinService.GetReceivedByAddress(user.DepositAddress, this.settings.MinConfirmationsForDeposit); if (receivedByAddress > user.LastCheckedReceivedAmountByAddress) { this.logger.Debug("New value for received by address is {0}. Old was {1}. Address is {2}.", receivedByAddress, user.LastCheckedReceivedAmountByAddress, user.DepositAddress); decimal recentlyReceived = receivedByAddress - user.LastCheckedReceivedAmountByAddress; user.LastCheckedReceivedAmountByAddress = receivedByAddress; user.Balance += recentlyReceived; this.logger.Info("User '{0}' deposited {1}!", user, recentlyReceived); context.Update(user); context.SaveChanges(); } } this.logger.Trace("(-)"); }
/// <summary>Checks quizes and removes those that are expired.</summary> private void CheckQuizesExpired() { this.logger.Trace("()"); using (BotDbContext context = this.contextFactory.CreateContext()) { foreach (QuizModel quiz in context.ActiveQuizes.ToList()) { if (DateTime.Now > (quiz.CreationTime + TimeSpan.FromMinutes(quiz.DurationMinutes))) { // Quiz expired. Return money to creator and remove the quiz. this.logger.Info("Quiz {0} expired.", quiz.Id); DiscordUserModel quizCreator = context.Users.Single(x => x.DiscordUserId == quiz.CreatorDiscordUserId); quizCreator.Balance += quiz.Reward; context.Update(quizCreator); context.ActiveQuizes.Remove(quiz); context.SaveChanges(); } } } this.logger.Trace("(-)"); }
/// <summary>Transfers <paramref name="amount"/> of money from <paramref name="sender"/> to <paramref name="userBeingTipped"/>.</summary> /// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void TipUser(IUser sender, IUser userBeingTipped, decimal amount) { this.logger.Trace("({0}:{1},{2}:'{3}',{4}:{5})", nameof(sender), sender.Id, nameof(userBeingTipped), userBeingTipped.Id, nameof(amount), amount); this.AssertAmountPositive(amount); this.AssertUsersNotEqual(sender, userBeingTipped); using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUserSender = this.GetOrCreateUser(context, sender); this.AssertBalanceIsSufficient(discordUserSender, amount); DiscordUserModel discordUserReceiver = this.GetOrCreateUser(context, userBeingTipped); discordUserSender.Balance -= amount; discordUserReceiver.Balance += amount; context.Update(discordUserReceiver); this.AddTipToHistory(context, amount, discordUserReceiver.DiscordUserId, discordUserSender.DiscordUserId); context.SaveChanges(); this.logger.Debug("User '{0}' tipped {1} to '{2}'", discordUserSender, discordUserReceiver, amount); } this.logger.Trace("(-)"); }
public AnswerToQuizResponseModel AnswerToQuiz(IUser user, string answer) { this.logger.Trace("({0}:'{1}')", nameof(answer), answer); if (answer.Length > 1024) { // We don't want to hash big strings. this.logger.Trace("(-)[ANSWER_TOO_LONG]"); return(new AnswerToQuizResponseModel() { Success = false }); } string answerHash = Cryptography.Hash(answer); using (BotDbContext context = this.contextFactory.CreateContext()) { foreach (QuizModel quiz in context.ActiveQuizes.ToList()) { if (DateTime.Now > (quiz.CreationTime + TimeSpan.FromMinutes(quiz.DurationMinutes))) { // Quiz expired but just not deleted yet. continue; } if (quiz.AnswerHash == answerHash) { DiscordUserModel winner = this.GetOrCreateUser(context, user); winner.Balance += quiz.Reward; context.Update(winner); context.ActiveQuizes.Remove(quiz); context.SaveChanges(); this.logger.Debug("User {0} solved quiz with hash {1} and received a reward of {2}.", winner, quiz.AnswerHash, quiz.Reward); var response = new AnswerToQuizResponseModel() { Success = true, Reward = quiz.Reward, QuizCreatorDiscordUserId = quiz.CreatorDiscordUserId, QuizQuestion = quiz.Question }; this.logger.Trace("(-)"); return(response); } } } this.logger.Trace("(-)[QUIZ_NOT_FOUND]"); return(new AnswerToQuizResponseModel() { Success = false }); }
private async Task GrantPermission(string discordUserId, Permission permission, BotDbContext dbContext, IDMChannel channel) { var user = await dbContext.Users.FirstOrDefaultAsync(u => u.DiscordUserId == discordUserId); user.UserPermissions.Add(new UserPermissions() { PermissionId = permission.Id, UserId = user.Id, User = user, Permission = permission }); dbContext.Update(user); await dbContext.SaveChangesAsync(); await channel.SendMessageAsync("Granted Permission"); }
public void AnswerToQuiz_FailsIfTimeIsCorrectButExpired() { this.testContext.CommandsManager.StartQuiz(this.caller, 2, Cryptography.Hash("1"), 2, string.Empty); using (BotDbContext dbContext = this.testContext.CreateDbContext()) { QuizModel quiz = dbContext.ActiveQuizes.First(); quiz.CreationTime = DateTime.Now - TimeSpan.FromMinutes(20); dbContext.Update(quiz); dbContext.SaveChanges(); } AnswerToQuizResponseModel answer = this.testContext.CommandsManager.AnswerToQuiz(this.caller, "1"); Assert.False(answer.Success); }
/// <summary>Withdraws given amount of money to specified address.</summary> /// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void Withdraw(IUser user, decimal amount, string address) { this.logger.Trace("({0}:{1},{2}:{3},{4}:'{5}')", nameof(user), user.Id, nameof(amount), amount, nameof(address), address); this.AssertAmountPositive(amount); if (amount < this.settings.MinWithdrawAmount) { this.logger.Trace("(-)[MIN_WITHDRAW_AMOUNT]"); throw new CommandExecutionException($"Minimal withdraw amount is {this.settings.MinWithdrawAmount} {this.settings.Ticker}."); } using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUser = this.GetOrCreateUser(context, user); // Don't allow withdrawals to deposit address. if (discordUser.DepositAddress != null && discordUser.DepositAddress == address) { this.logger.Trace("(-)[WITHDRAWAL_TO_DEPOSIT_ADDR]"); throw new CommandExecutionException("You can't withdraw to your own deposit address!"); } decimal amountToSend = amount - this.settings.NetworkFee; this.logger.Trace("(The amount after fee: {0})", amountToSend); this.AssertBalanceIsSufficient(discordUser, amountToSend); try { this.nodeIntegration.Withdraw(amountToSend, address); this.logger.Debug("User '{0}' withdrew {1} to address '{2}'. After fee subtracted '{3}'", discordUser, amount, address, amountToSend); } catch (InvalidAddressException) { this.logger.Trace("(-)[INVALID_ADDRESS]"); throw new CommandExecutionException("Address specified is invalid."); } discordUser.Balance -= amount; context.Update(discordUser); context.SaveChanges(); } this.logger.Trace("(-)"); }
public QuizTests() { this.testContext = new TestContext(); this.caller = this.testContext.SetupUser(1, "caller"); // That will create a user in db. this.testContext.CommandsManager.GetUserBalance(this.caller); using (BotDbContext dbContext = this.testContext.CreateDbContext()) { DiscordUserModel user = dbContext.Users.First(); user.Balance = 10; dbContext.Update(user); dbContext.SaveChanges(); } }
/// <summary>Gets deposit address for a user.</summary> /// <exception cref="OutOfDepositAddressesException">Thrown when bot ran out of unused deposit addresses.</exception> public string GetDepositAddress(IUser user) { this.logger.Trace("({0}:{1})", nameof(user), user.Id); using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUser = this.GetOrCreateUser(context, user); string depositAddress = discordUser.DepositAddress; // Assign deposit address if it wasn't assigned it. if (depositAddress == null) { this.logger.Trace("Assigning deposit address for '{0}'.", discordUser); AddressModel unusedAddress = context.UnusedAddresses.FirstOrDefault(); if (unusedAddress == null) { var message = "Bot ran out of deposit addresses!"; this.logger.Fatal(message); this.fatalNotifier.NotifySupport(message); this.logger.Trace("(-)[NO_ADDRESSES]"); throw new OutOfDepositAddressesException(); } context.UnusedAddresses.Remove(unusedAddress); depositAddress = unusedAddress.Address; discordUser.DepositAddress = depositAddress; context.Update(discordUser); context.SaveChanges(); } this.logger.Trace("(-):'{0}'", depositAddress); return(depositAddress); } }
/// <summary>Withdraws given amount of money to specified address.</summary> /// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void Withdraw(IUser user, decimal amount, string address) { this.logger.Trace("({0}:{1},{2}:{3},{4}:'{5}')", nameof(user), user.Id, nameof(amount), amount, nameof(address), address); this.AssertAmountPositive(amount); using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUser = this.GetOrCreateUser(context, user); this.AssertBalanceIsSufficient(discordUser, amount); if (amount < this.settings.MinWithdrawAmount) { this.logger.Trace("(-)[MIN_WITHDRAW_AMOUNT]"); throw new CommandExecutionException($"Minimal withdraw amount is {this.settings.MinWithdrawAmount} {this.settings.Ticker}."); } try { this.nodeIntegration.Withdraw(amount, address); this.logger.Debug("User '{0}' withdrew {1} to address '{2}'.", discordUser, amount, address); } catch (InvalidAddressException) { this.logger.Trace("(-)[INVALID_ADDRESS]"); throw new CommandExecutionException("Address specified is invalid."); } discordUser.Balance -= amount; context.Update(discordUser); context.SaveChanges(); } this.logger.Trace("(-)"); }
public RandomlyTipUsersTests() { this.testContext = new TestContext(); this.caller = this.testContext.SetupUser(1, "caller"); // That will create a user in db. this.testContext.CommandsManager.GetUserBalance(this.caller); using (BotDbContext dbContext = this.testContext.CreateDbContext()) { DiscordUserModel user = dbContext.Users.First(); user.Balance = 10; dbContext.Update(user); dbContext.SaveChanges(); } this.onlineUsers = new List <IUser>(); for (var i = 0; i < 10; i++) { IUser user = this.testContext.SetupUser((ulong)(i + 2), i.ToString()); this.onlineUsers.Add(user); } }
/// <summary>Transfers <paramref name="amount"/> of money from <paramref name="sender"/> to <paramref name="userBeingTipped"/>.</summary> /// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void TipUser(IUser sender, IUser userBeingTipped, decimal amount) { this.logger.Trace("({0}:{1},{2}:'{3}',{4}:{5})", nameof(sender), sender.Id, nameof(userBeingTipped), userBeingTipped.Id, nameof(amount), amount); this.AssertAmountPositive(amount); this.AssertUsersNotEqual(sender, userBeingTipped); if (amount < this.settings.MinTipAmount) { this.logger.Trace("(-)[TIP TOO SMALL]'"); throw new CommandExecutionException($"Minimal tip is {this.settings.MinTipAmount}."); } using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUserSender = this.GetOrCreateUser(context, sender); this.AssertBalanceIsSufficient(discordUserSender, amount); DiscordUserModel discordUserReceiver = this.GetOrCreateUser(context, userBeingTipped); discordUserSender.Balance -= amount; discordUserReceiver.Balance += amount; context.Update(discordUserReceiver); this.AddTipToHistory(context, amount, discordUserReceiver.DiscordUserId, discordUserReceiver.Username, discordUserSender.DiscordUserId, discordUserSender.Username); context.SaveChanges(); this.logger.Debug("User '{0}' tipped {1} to '{2}'", discordUserSender, discordUserReceiver, amount); } this.logger.Trace("(-)"); }
/// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> /// <returns>List of users that were tipped.</returns> public List <DiscordUserModel> RandomlyTipUsers(IUser caller, List <IUser> onlineUsers, decimal amount) { this.logger.Trace("({0}:{1},{2}.{3}:{4},{5}:{6})", nameof(caller), caller.Id, nameof(onlineUsers), nameof(onlineUsers.Count), onlineUsers.Count, nameof(amount), amount); this.AssertAmountPositive(amount); var coinsToTip = (int)amount; if (coinsToTip < 1) { this.logger.Trace("(-)[AMOUNT_TOO_SMALL]'"); throw new CommandExecutionException("Amount can't be less 1."); } if (onlineUsers.Count == 0) { this.logger.Trace("(-)[NO_USERS_ONLINE]'"); throw new CommandExecutionException("There are no users online!"); } if (coinsToTip > onlineUsers.Count) { this.logger.Trace("Coins to tip was set to amount of users."); coinsToTip = onlineUsers.Count; } using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUserCreator = this.GetOrCreateUser(context, caller); this.AssertBalanceIsSufficient(discordUserCreator, coinsToTip); var chosenDiscordUsers = new List <DiscordUserModel>(coinsToTip); for (int i = 0; i < coinsToTip; i++) { int userIndex = this.random.Next(onlineUsers.Count); IUser chosenUser = onlineUsers[userIndex]; onlineUsers.Remove(chosenUser); DiscordUserModel chosenDiscordUser = this.GetOrCreateUser(context, chosenUser); chosenDiscordUsers.Add(chosenDiscordUser); } discordUserCreator.Balance -= coinsToTip; context.Update(discordUserCreator); foreach (DiscordUserModel discordUser in chosenDiscordUsers) { discordUser.Balance += 1; context.Update(discordUser); this.AddTipToHistory(context, 1, discordUser.DiscordUserId, discordUserCreator.DiscordUserId); this.logger.Debug("User '{0}' was randomly tipped.", discordUser); } this.logger.Debug("User '{0}' tipped {1} users one coin each.", discordUserCreator, chosenDiscordUsers.Count); context.SaveChanges(); this.logger.Trace("(-)"); return(chosenDiscordUsers); } }
/// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void StartQuiz(IUser user, decimal amount, string answerSHA256, int durationMinutes, string question) { this.logger.Trace("({0}:{1},{2}:{3},{4}:'{5}',{6}:{7},{8}:'{9}')", nameof(user), user.Id, nameof(amount), amount, nameof(answerSHA256), answerSHA256, nameof(durationMinutes), durationMinutes, nameof(question), question); this.AssertAmountPositive(amount); answerSHA256 = answerSHA256.ToLower(); if (answerSHA256.Length != 64) { this.logger.Trace("(-)[INCORRECT_HASH]'"); throw new CommandExecutionException("SHA256 hash should contain 64 characters!"); } if (durationMinutes < 1) { this.logger.Trace("(-)[INCORRECT_DURATION]'"); throw new CommandExecutionException("Duration in minutes can't be less than 1!"); } var maxQuestionLenght = 1024; if (question.Length > maxQuestionLenght) { this.logger.Trace("(-)[QUESTION_TOO_LONG]'"); throw new CommandExecutionException($"Questions longer than {maxQuestionLenght} characters are not allowed!"); } using (BotDbContext context = this.contextFactory.CreateContext()) { if (context.ActiveQuizes.Any(x => x.AnswerHash == answerSHA256)) { this.logger.Trace("(-)[HASH_ALREADY_EXISTS]"); throw new CommandExecutionException("There is already a quiz with that answer hash!"); } DiscordUserModel discordUser = this.GetOrCreateUser(context, user); this.AssertBalanceIsSufficient(discordUser, amount); if (amount < this.settings.MinQuizAmount) { this.logger.Trace("(-)[AMOUNT_TOO_LOW]"); throw new CommandExecutionException($"Minimal quiz reward is {this.settings.MinQuizAmount} {this.settings.Ticker}!"); } var quiz = new QuizModel() { AnswerHash = answerSHA256, CreationTime = DateTime.Now, CreatorDiscordUserId = discordUser.DiscordUserId, DurationMinutes = durationMinutes, Question = question, Reward = amount }; discordUser.Balance -= amount; context.Update(discordUser); context.ActiveQuizes.Add(quiz); context.SaveChanges(); this.logger.Debug("Quiz with reward {0} and answer hash '{1}' was created by user '{2}'.", amount, answerSHA256, discordUser); } this.logger.Trace("(-)"); }
/// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> /// <returns>List of users that were tipped.</returns> public List <DiscordUserModel> RandomlyTipUsers(IUser caller, List <IUser> onlineUsers, decimal totalAmount, decimal tipAmount) { this.logger.Trace("({0}:{1},{2}.{3}:{4},{5}:{6})", nameof(caller), caller.Id, nameof(onlineUsers), nameof(onlineUsers.Count), onlineUsers.Count, nameof(totalAmount), totalAmount); this.AssertAmountPositive(totalAmount); if (tipAmount < this.settings.MinMakeItRainTipAmount) { this.logger.Trace("(-)[TIP_SIZE_TOO_SMALL]'"); throw new CommandExecutionException($"Tip amount can't be less than {this.settings.MinMakeItRainTipAmount}."); } if (totalAmount < tipAmount) { this.logger.Trace("(-)[AMOUNT_TOO_SMALL]'"); throw new CommandExecutionException("Total amount for tipping can't be less than specified tip amount."); } if (onlineUsers.Count == 0) { this.logger.Trace("(-)[NO_USERS_ONLINE]'"); throw new CommandExecutionException("There are no users online!"); } var tipsCount = (int)(totalAmount / tipAmount); if (tipsCount > onlineUsers.Count) { this.logger.Trace("Coins to tip was set to amount of users."); tipsCount = onlineUsers.Count; } decimal amountToSpend = tipAmount * tipsCount; using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUserCreator = this.GetOrCreateUser(context, caller); this.AssertBalanceIsSufficient(discordUserCreator, amountToSpend); var chosenDiscordUsers = new List <DiscordUserModel>(tipsCount); for (var i = 0; i < tipsCount; i++) { int userIndex = this.random.Next(onlineUsers.Count); IUser chosenUser = onlineUsers[userIndex]; onlineUsers.Remove(chosenUser); DiscordUserModel chosenDiscordUser = this.GetOrCreateUser(context, chosenUser); chosenDiscordUsers.Add(chosenDiscordUser); } discordUserCreator.Balance -= amountToSpend; context.Update(discordUserCreator); foreach (DiscordUserModel discordUser in chosenDiscordUsers) { discordUser.Balance += tipAmount; context.Update(discordUser); this.AddTipToHistory(context, tipAmount, discordUser.DiscordUserId, discordUser.Username, discordUserCreator.DiscordUserId, discordUserCreator.Username); this.logger.Debug("User '{0}' was randomly tipped.", discordUser); } this.logger.Debug("User '{0}' tipped {1} users {2} coins each.", discordUserCreator, chosenDiscordUsers.Count, tipAmount); context.SaveChanges(); this.logger.Trace("(-)"); return(chosenDiscordUsers); } }
/// <summary>Withdraws given amount of money to specified address.</summary> /// <exception cref="CommandExecutionException">Thrown when user supplied invalid input data.</exception> public void Withdraw(IUser user, decimal amount, string address) { this.logger.Trace("({0}:{1},{2}:{3},{4}:'{5}')", nameof(user), user.Id, nameof(amount), amount, nameof(address), address); this.AssertAmountPositive(amount); if (amount < this.settings.MinWithdrawAmount) { this.logger.Trace("(-)[MIN_WITHDRAW_AMOUNT]"); throw new CommandExecutionException($"Minimal withdraw amount is {this.settings.MinWithdrawAmount} {this.settings.Ticker}."); } using (BotDbContext context = this.contextFactory.CreateContext()) { DiscordUserModel discordUser = this.GetOrCreateUser(context, user); // Don't allow withdrawals to deposit address. if (discordUser.DepositAddress != null && discordUser.DepositAddress == address) { this.logger.Trace("(-)[WITHDRAWAL_TO_DEPOSIT_ADDR]"); throw new CommandExecutionException("You can't withdraw to your own deposit address!"); } decimal amountToSend = amount - this.settings.NetworkFee; this.logger.Trace("(The amount after fee: {0})", amountToSend); this.AssertBalanceIsSufficient(discordUser, amountToSend); try { var withdrawResult = this.nodeIntegration.Withdraw(amountToSend, address); discordUser.Balance -= amount; context.Update(discordUser); context.SaveChanges(); var withdrawHistoryItem = new WithdrawHistory { DiscordUserId = discordUser.DiscordUserId, Amount = amountToSend, ToAddress = address, TransactionId = withdrawResult.TransactionId, WithdrawTime = DateTime.UtcNow }; context.Add(withdrawHistoryItem); context.SaveChanges(); this.logger.Debug("User '{0}' withdrew {1} to address '{2}'. After fee subtracted '{3}'", discordUser, amount, address, amountToSend); } catch (InvalidAddressException) { this.logger.Trace("(-)[INVALID_ADDRESS]"); throw new CommandExecutionException("Address specified is invalid."); } catch (Exception ex) { this.logger.Trace("(-)[FAILED_WITHDRAW]"); this.logger.Debug($"(WITHDRAW_ERROR)[{ex.Message}]"); throw new CommandExecutionException($"Failed to withdraw. Contact {this.settings.Discord.SupportUsername}"); } } this.logger.Trace("(-)"); }