MintCommand.cs
1 using BiblioTech.Options; 2 using CodexContractsPlugin; 3 using GethPlugin; 4 using Logging; 5 using Utils; 6 7 namespace BiblioTech.Commands 8 { 9 public class MintCommand : BaseGethCommand 10 { 11 private readonly UserOption optionalUser = new UserOption( 12 description: "If set, mint tokens for this user. (Optional, admin-only)", 13 isRequired: false); 14 private readonly UserAssociateCommand userAssociateCommand; 15 private readonly ILog log; 16 17 public MintCommand(UserAssociateCommand userAssociateCommand) 18 { 19 this.userAssociateCommand = userAssociateCommand; 20 log = Program.Log; 21 } 22 23 public override string Name => "mint"; 24 public override string StartingMessage => RandomBusyMessage.Get(); 25 public override string Description => "Transfer and/or mint some TestTokens and Eth to the user if their balance is low."; 26 public override CommandOption[] Options => new[] { optionalUser }; 27 28 protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts) 29 { 30 var userId = GetUserFromCommand(optionalUser, context); 31 var addr = Program.UserRepo.GetCurrentAddressForUser(userId); 32 if (addr == null) 33 { 34 await context.Followup($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first."); 35 await Program.AdminChecker.SendInAdminChannel($"User {Mention(userId)} used '/{Name}' but address has not been set."); 36 return; 37 } 38 39 log.Debug($"Running mint command for {userId} with address {addr}..."); 40 var report = new List<string>(); 41 42 Transaction<Ether>? sentEth = null; 43 Transaction<TestToken>? mintedTokens = null; 44 45 await Task.Run(async () => 46 { 47 sentEth = ProcessEth(gethNode, addr, report); 48 mintedTokens = await ProcessTestTokens(contracts, addr, report); 49 }); 50 51 var reportLine = string.Join(Environment.NewLine, report); 52 Program.UserRepo.AddMintEventForUser(userId, addr, sentEth, mintedTokens); 53 await Program.AdminChecker.SendInAdminChannel($"User {Mention(userId)} used '/{Name}' successfully. ({reportLine})"); 54 55 await context.Followup(reportLine); 56 } 57 58 private async Task<Transaction<TestToken>?> ProcessTestTokens(ICodexContracts contracts, EthAddress addr, List<string> report) 59 { 60 if (IsTestTokenBalanceOverLimit(contracts, addr)) 61 { 62 log.Debug("TestToken balance is over threshold."); 63 report.Add("TestToken balance over threshold. (No TestTokens sent or minted.)"); 64 return null; 65 } 66 67 var sent = await TransferTestTokens(contracts, addr, report); 68 var minted = await MintTestTokens(contracts, addr, report); 69 70 return new Transaction<TestToken>(sent.Item1, minted.Item1, $"{sent.Item2},{minted.Item2}"); 71 } 72 73 private Transaction<Ether>? ProcessEth(IGethNode gethNode, EthAddress addr, List<string> report) 74 { 75 if (IsEthBalanceOverLimit(gethNode, addr)) 76 { 77 log.Debug("Eth balance is over threshold."); 78 report.Add("Eth balance is over threshold. (No Eth sent.)"); 79 return null; 80 } 81 var eth = Program.Config.SendEth.Eth(); 82 log.Debug($"Sending {eth}..."); 83 var transaction = gethNode.SendEth(addr, eth); 84 report.Add($"Sent {eth} {FormatTransactionLink(transaction)}"); 85 return new Transaction<Ether>(eth, 0.Eth(), transaction); 86 } 87 88 private async Task<(TestToken, string)> MintTestTokens(ICodexContracts contracts, EthAddress addr, List<string> report) 89 { 90 var nothing = (0.TstWei(), string.Empty); 91 if (Program.Config.MintTT < 1) 92 { 93 log.Debug("Skip minting TST: configured amount is less than 1."); 94 return nothing; 95 } 96 97 try 98 { 99 var tokens = Program.Config.MintTT.TstWei(); 100 log.Debug($"Minting {tokens}..."); 101 var transaction = contracts.MintTestTokens(addr, tokens); 102 report.Add($"Minted {tokens} {FormatTransactionLink(transaction)}"); 103 return (tokens, transaction); 104 } 105 catch (Exception ex) 106 { 107 report.Add("Minter: I'm sorry! Something went unexpectedly wrong. (Admins have been notified)"); 108 await Program.AdminChecker.SendInAdminChannel($"{nameof(MintCommand)} {nameof(MintTestTokens)} failed with: {ex}"); 109 } 110 return nothing; 111 } 112 113 private async Task<(TestToken, string)> TransferTestTokens(ICodexContracts contracts, EthAddress addr, List<string> report) 114 { 115 var nothing = (0.TstWei(), string.Empty); 116 if (Program.Config.SendTT < 1) 117 { 118 log.Debug("Skip transferring TST: configured amount is less than 1."); 119 return nothing; 120 } 121 if (Program.GethLink == null) 122 { 123 log.Debug("Skip transferring TST: GethLink not available."); 124 report.Add("Transaction operations are currently not available."); 125 return nothing; 126 } 127 128 try 129 { 130 var current = contracts.GetTestTokenBalance(Program.GethLink.Node.CurrentAddress); 131 var amount = Program.Config.SendTT.TstWei(); 132 if (current.TstWei <= amount.TstWei) 133 { 134 log.Debug($"Unable to transfer TST: Bot has: {current} - Transfer amount: {amount}"); 135 report.Add("Unable to send TestTokens: Bot doesn't have enough! (Admins have been notified)"); 136 await Program.AdminChecker.SendInAdminChannel($"{nameof(MintCommand)} failed: Bot has insufficient tokens."); 137 return nothing; 138 } 139 140 log.Debug($"Sending {amount}..."); 141 var transaction = contracts.TransferTestTokens(addr, amount); 142 report.Add($"Transferred {amount} {FormatTransactionLink(transaction)}"); 143 return (amount, transaction); 144 } 145 catch (Exception ex) 146 { 147 report.Add("Transfer: I'm sorry! Something went unexpectedly wrong. (Admins have been notified)"); 148 await Program.AdminChecker.SendInAdminChannel($"{nameof(MintCommand)} {nameof(TransferTestTokens)} failed with: {ex}"); 149 } 150 return nothing; 151 } 152 153 private bool IsEthBalanceOverLimit(IGethNode gethNode, EthAddress addr) 154 { 155 var eth = gethNode.GetEthBalance(addr); 156 return eth > Program.Config.SendEth.Eth(); 157 } 158 159 private bool IsTestTokenBalanceOverLimit(ICodexContracts contracts, EthAddress addr) 160 { 161 var testTokens = contracts.GetTestTokenBalance(addr); 162 163 if (Program.Config.MintTT > 0 && testTokens > Program.Config.MintTT.TstWei()) return true; 164 if (Program.Config.SendTT > 0 && testTokens > Program.Config.SendTT.TstWei()) return true; 165 166 return false; 167 } 168 169 private string FormatTransactionLink(string transaction) 170 { 171 var url = Program.Config.TransactionLinkFormat.Replace("<ID>", transaction); 172 return $"- [View on block explorer](<{url}>){Environment.NewLine}Transaction ID - `{transaction}`"; 173 } 174 } 175 }