diff --git a/contracts/burndollar.js b/contracts/burndollar.js new file mode 100644 index 00000000..e64e88c5 --- /dev/null +++ b/contracts/burndollar.js @@ -0,0 +1,441 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable valid-typeof */ +/* eslint-disable max-len */ +/* eslint-disable no-continue */ +/* global actions, api */ + +const stablePairArray = ['SWAP.HBD', 'SWAP.USDT', 'SWAP.DAI', 'SWAP.USDC']; + +// begin utility functions +const countDecimals = value => api.BigNumber(value).dp(); +const verifyTokenCreation = async (symbolFind) => { + const createD = await api.db.findOneInTable('tokens', 'tokens', { symbol: symbolFind }); + + if (!createD) { + return false; + } + return true; +}; + +const verifyTokenBalance = async (account, amount, symbolFind) => { + const findTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: symbolFind }); + + if (findTokenBalance && api.BigNumber(findTokenBalance.balance).gte(amount)) { + return true; + } + return false; +}; + +const checkStablePosition = (tokenPair) => { + const [firstToken, secondToken] = tokenPair.split(':'); + + if (stablePairArray.includes(firstToken)) { + return 'base'; + } if (stablePairArray.includes(secondToken)) { + return 'quote'; + } + return false; +}; + +const findMarketPools = async (parentSymbol, toggle) => { + const childSymbol = `${parentSymbol}.D`; + let poolData; + + if (toggle === 'stable') { + const stableParentArray = [`${parentSymbol}`, `${childSymbol}`]; + const stableResults = stableParentArray.flatMap(pElement => stablePairArray.flatMap(sElement => [ + `${sElement}:${pElement}`, + `${pElement}:${sElement}`, + ])); + + const multiPoolData = await api.db.findInTable('marketpools', 'pools', { + tokenPair: { $in: stableResults }, + }); + + // in the case token is in multiple pools find the one with the largest quantity of base+quote + poolData = multiPoolData.length > 0 ? [multiPoolData.reduce((max, item) => { + const totalQuantity = item.baseQuantity + item.quoteQuantity; + return totalQuantity > (max.baseQuantity + max.quoteQuantity) ? item : max; + }, multiPoolData[0])] : []; + } + + if (toggle === 'market') { + const marketParentArray = [`${parentSymbol}:${childSymbol}`, `${childSymbol}:${parentSymbol}`]; + + poolData = await api.db.findInTable('marketpools', 'pools', { + tokenPair: { $in: marketParentArray }, + }); + } + + const validPools = poolData.map(pool => ({ + tokenPair: pool.tokenPair, + basePrice: pool.basePrice || '0', + quotePrice: pool.quotePrice || '0', + baseQuantity: pool.baseQuantity || '0', + quoteQuantity: pool.quoteQuantity || '0', + })); + return validPools.length > 0 ? validPools : null; +}; + +const calcParentPool = async (name, pool, tokenPriceUSD, precision) => { + const [firstToken, secondToken] = pool.tokenPair.split(':'); + let quoteOrBasePosition; + let otherTokenPriceUSD; + let halfPoolinUSD; + let fullPoolinUSD; + let parentTokenPrice; + let returnObject = {}; + + if (name.includes(firstToken)) { + quoteOrBasePosition = 'base'; + } if (name.includes(secondToken)) { + quoteOrBasePosition = 'quote'; + } + + if (quoteOrBasePosition && quoteOrBasePosition === 'base') { + otherTokenPriceUSD = api.BigNumber(pool.quotePrice).multipliedBy(tokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + if (!name.includes('.D')) { + parentTokenPrice = api.BigNumber(otherTokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + } else { + parentTokenPrice = api.BigNumber(tokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + } + halfPoolinUSD = api.BigNumber(otherTokenPriceUSD).multipliedBy(pool.baseQuantity).toFixed(precision, api.BigNumber.ROUND_DOWN); + // Conservative value of the pool: multiply the value of the half pool by 1.95 + fullPoolinUSD = api.BigNumber(halfPoolinUSD).multipliedBy(1.95).toFixed(precision, api.BigNumber.ROUND_DOWN); + returnObject = { + quoteToken: firstToken, quotePriceUSD: otherTokenPriceUSD, baseToken: secondToken, basePriceUSD: tokenPriceUSD, precision, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, + }; + } else if (quoteOrBasePosition && quoteOrBasePosition === 'quote') { // perform calc based on second position === quote + // we have the price of one token from the stable pool calc, we need to calc the price of the token's pair + otherTokenPriceUSD = api.BigNumber(pool.basePrice).multipliedBy(tokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + + if (!name.includes('.D')) { + parentTokenPrice = api.BigNumber(tokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + } else { + parentTokenPrice = api.BigNumber(otherTokenPriceUSD).toFixed(precision, api.BigNumber.ROUND_DOWN); + } + halfPoolinUSD = api.BigNumber(otherTokenPriceUSD).multipliedBy(pool.quoteQuantity).toFixed(precision, api.BigNumber.ROUND_DOWN); + // Conservative value of the pool: multiply the value of the half pool by 1.95 + fullPoolinUSD = api.BigNumber(halfPoolinUSD).multipliedBy(1.95).toFixed(precision, api.BigNumber.ROUND_DOWN); + returnObject = { + quoteToken: firstToken, quotePriceUSD: tokenPriceUSD, baseToken: secondToken, basePriceUSD: otherTokenPriceUSD, precision, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, + }; + } + return returnObject; +}; + +const isTokenTransferVerified = (result, from, to, symbol, quantity, eventStr) => { + if (result.errors === undefined + && result.events && result.events.find(el => (el.contract === 'tokens' || el.contract === 'burndollar') && el.event === eventStr + && el.data.from === from && el.data.to === to && api.BigNumber(el.data.quantity).eq(quantity) && el.data.symbol === symbol) !== undefined) { + return true; + } + + return false; +}; + +const burnParentTokens = async (amount, fee, burnSymbol, toAccount, beedParams, isSignedWithActiveKey) => { + if (api.BigNumber(fee).gt(0)) { + const res = await api.executeSmartContract('tokens', 'transfer', { + to: toAccount, symbol: burnSymbol, quantity: fee, isSignedWithActiveKey, + }); + + if (!isTokenTransferVerified(res, api.sender, toAccount, burnSymbol, fee, 'transfer')) { + return false; + } + } + + const res2 = await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: burnSymbol, quantity: amount, isSignedWithActiveKey, + }); + + + const res3 = await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: beedParams.burnToken, quantity: beedParams.burnUsageFee, isSignedWithActiveKey, + }); + + if (!isTokenTransferVerified(res2, api.sender, 'null', burnSymbol, amount, 'transfer')) { + return false; + } + if (!isTokenTransferVerified(res3, api.sender, 'null', beedParams.burnToken, beedParams.burnUsageFee, 'transfer')) { + return false; + } + return true; +}; +// end utility functions + +actions.createSSC = async () => { + const tableExists = await api.db.tableExists('params'); + if (tableExists === false) { + await api.db.createTable('params'); + await api.db.createTable('burnpair', ['symbol', 'parentSymbol']); + const params = {}; + params.issueDTokenFee = '1000'; // BEED quantity + params.updateParamsFee = '100'; // BEED quantity + params.burnUsageFee = '1'; // BEED quantity + params.minAmountConvertible = '1'; // XXX.d token minimum convert quantity issuer can update this; + params.dTokenToIssuer = '1000'; // XXX.d token issued + params.burnToken = 'BEED'; + + await api.db.insert('params', params); + } +}; + +actions.updateParams = async (payload) => { + if (api.sender !== api.owner) return; + + const { + issueDTokenFee, + updateParamsFee, + burnUsageFee, + minAmountConvertible, + dTokenToIssuer, + burnToken, + } = payload; + + const params = await api.db.findOne('params', {}); + if (issueDTokenFee && typeof issueDTokenFee === 'string' && !api.BigNumber(issueDTokenFee).isNaN() && api.BigNumber(issueDTokenFee).gte(1)) { + params.issueDTokenFee = issueDTokenFee; + } + if (updateParamsFee && typeof updateParamsFee === 'string' && !api.BigNumber(updateParamsFee).isNaN() && api.BigNumber(updateParamsFee).gte(1)) { + params.updateParamsFee = updateParamsFee; + } + if (burnUsageFee && typeof burnUsageFee === 'string' && !api.BigNumber(burnUsageFee).isNaN() && api.BigNumber(burnUsageFee).gte(1)) { + params.burnUsageFee = burnUsageFee; + } + if (minAmountConvertible && typeof minAmountConvertible === 'string' && !minAmountConvertible.isNaN() && minAmountConvertible.gte(1)) { + params.minAmountConvertible = minAmountConvertible; + } + if (dTokenToIssuer && typeof dTokenToIssuer === 'string' && !api.BigNumber(dTokenToIssuer).isNaN() && api.BigNumber(dTokenToIssuer).gte(1)) { + params.dTokenToIssuer = dTokenToIssuer; + } + if (burnToken && typeof burnToken === 'string') { + const findToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: burnToken }); + + if (findToken) { + params.burnToken = burnToken; + } + } + + await api.db.update('params', params); +}; + +actions.createTokenD = async (payload) => { + const { + symbol, isSignedWithActiveKey, burnRouting, feePercentage, + } = payload; + + const burnPairParams = {}; + const params = await api.db.findOne('params', {}); + const { + issueDTokenFee, + } = params; + + const beedTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: params.burnToken }); + const authorizedCreation = beedTokenBalance && api.BigNumber(beedTokenBalance.balance).gte(issueDTokenFee); + + if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(authorizedCreation, 'you must have enough BEED tokens cover the creation fees') + && api.assert(symbol && typeof symbol === 'string' && symbol.length <= 8 && symbol.length > 0 && !symbol.includes('.D'), 'symbol must be string of length 8 or less to create a xxx.D token') + ) { + const tokenParent = await api.db.findOneInTable('tokens', 'tokens', { symbol }); + const finalRouting = burnRouting === undefined ? 'null' : burnRouting; + + if (api.assert(tokenParent.issuer === api.sender, 'You must be the token issuer in order to issue D token') + && api.assert(api.isValidAccountName(finalRouting), 'burn routing must be a valid Hive account name') + ) { + if (api.assert(feePercentage && typeof feePercentage === 'string' && !api.BigNumber(feePercentage).isNaN() && api.BigNumber(feePercentage).gte(0) && api.BigNumber(feePercentage).lte(1) && countDecimals(feePercentage) <= 4, 'fee percentage must be between 0 and 1 / 0% and 100%') + ) { + let finalName = ''; + let dSymbol = ''; + dSymbol = `${symbol}.D`; + const tokenDExists = await api.db.findOneInTable('tokens', 'tokens', { symbol: dSymbol }); + if (api.assert(tokenDExists === null, 'D token must not already exist') + && api.assert((tokenParent.precision > 0) && (Number.isInteger(tokenParent.precision)), 'invalid precision') + ) { + finalName = `${symbol} stablecoin`; + const newToken = { + symbol: dSymbol, + name: finalName, + precision: tokenParent.precision, + maxSupply: `${Number.MAX_SAFE_INTEGER}`, + }; + + await api.executeSmartContract('tokens', 'create', newToken); + + const tokenCreated = await verifyTokenCreation(dSymbol); + + if (!api.assert(tokenCreated, 'Token creation failed')) { + return false; + } + + burnPairParams.issuer = api.sender; + burnPairParams.symbol = dSymbol; + burnPairParams.precision = tokenParent.precision; + burnPairParams.parentSymbol = symbol; + burnPairParams.burnRouting = finalRouting; + burnPairParams.feePercentage = feePercentage; + + await api.db.insert('burnpair', burnPairParams); + + // issue a number of XXX.D token to token issuer, issuer must create a market pool in order for conversions to occur(see actions.convert code) + await api.executeSmartContract('tokens', 'issue', { + to: api.sender, symbol: dSymbol, quantity: params.dTokenToIssuer, + }); + + if (api.BigNumber(issueDTokenFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: params.burnToken, quantity: issueDTokenFee, isSignedWithActiveKey, + }); + } + api.emit('issued new token dollar stablecoin', { + convertPercentage: feePercentage, feeRouting: burnPairParams.burnRouting, dSymbol, + }); + } + } + } + } +}; + +actions.updateBurnPair = async (payload) => { + const { + symbol, + burnRouting, + feePercentage, + isSignedWithActiveKey, + } = payload; + + const finalRouting = burnRouting === undefined ? 'null' : burnRouting; + + if (api.assert(api.isValidAccountName(finalRouting), 'account for burn routing must exist')) { + if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(symbol && typeof symbol === 'string', 'symbol must be string') + && api.assert(finalRouting && typeof finalRouting === 'string', 'finalRouting must be string or null') + && api.assert(feePercentage && typeof feePercentage === 'string' && !api.BigNumber(feePercentage).isNaN() && api.BigNumber(feePercentage).gte(0) && api.BigNumber(feePercentage).lte(1) && countDecimals(feePercentage) <= 4, 'fee percentage must be between 0 and 1 / 0% and 100%') + ) { + const token = await api.db.findOne('burnpair', { symbol }); + + if (!api.assert(token !== null && token !== undefined, 'D token must exist')) { + return false; + } + + if (token) { + if (api.assert(token.issuer === api.sender, 'must be the issuer')) { + const params = await api.db.findOne('params', {}); + const { updateParamsFee } = params; + const beedTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: params.burnToken }); + const authorizedCreation = beedTokenBalance && api.BigNumber(beedTokenBalance.balance).gte(updateParamsFee); + + if (api.assert(authorizedCreation, 'you must have enough BEED tokens to cover the update properties fee')) { + token.burnRouting = finalRouting; + token.feePercentage = feePercentage; + await api.db.update('burnpair', token); + + if (api.BigNumber(updateParamsFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: params.burnToken, quantity: updateParamsFee, isSignedWithActiveKey, + }); + } + + api.emit('updated params', { + symbol, burnRouting, feePercentage, + }); + } + } + } + } + } +}; + +actions.convert = async (payload) => { + const { + symbol, quantity, isSignedWithActiveKey, + } = payload; + + if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(quantity && typeof quantity === 'string' && !api.BigNumber(quantity).isNaN(), 'invalid params quantity') + && api.assert(symbol && typeof symbol === 'string' && symbol.length > 0 && symbol.length <= 10, 'symbol must be string')) { + const contractParams = await api.db.findOne('params', {}); + const parentPairParams = await api.db.findOne('burnpair', { parentSymbol: symbol }); + const qtyAsBigNum = api.BigNumber(quantity); + if (api.assert(parentPairParams, 'parent symbol must have a child .D token') + && api.assert(countDecimals(quantity) <= parentPairParams.precision, 'symbol precision mismatch') + && api.assert(qtyAsBigNum.gte(contractParams.minAmountConvertible), 'amount to convert must be >= 1')) { + const hasEnoughUtilityToken = await verifyTokenBalance(api.sender, contractParams.burnUsageFee, contractParams.burnToken); + const hasEnoughParentBalance = await verifyTokenBalance(api.sender, qtyAsBigNum, symbol); + const hasEnoughStablePool = await findMarketPools(symbol, 'stable'); + const hasEnoughMarketPool = await findMarketPools(symbol, 'market'); + + if (api.assert(hasEnoughParentBalance, 'not enough token balance') + && api.assert(hasEnoughUtilityToken, 'not enough utility tokens') + && api.assert(hasEnoughStablePool, 'token must be in pool with a stable coin') + && api.assert(hasEnoughMarketPool, 'token must be in pool with xxx.d token')) { + const quoteOrBase = checkStablePosition(hasEnoughStablePool[0].tokenPair); + let calcResultParentPool; + + if (quoteOrBase && quoteOrBase === 'base') { + const stablePrice = hasEnoughStablePool[0].basePrice; + const stableQuant = hasEnoughStablePool[0].baseQuantity; + const tokenNameBase = hasEnoughStablePool[0].tokenPair.split(':')[1]; + const stableUSDValue = api.BigNumber(stablePrice).multipliedBy(stableQuant).toFixed(parentPairParams.precision, api.BigNumber.ROUND_DOWN); + + // Calulate the conservative value of the pool multiple the value of one side by 1.95, 1.95 used to ensure pools are not overvalued + const finalValueQuote = api.BigNumber(stableUSDValue).multipliedBy(1.95).toFixed(parentPairParams.precision, api.BigNumber.ROUND_DOWN); + + // users to be be informed of $500 barrier to entry/ the difference of $100 (500 vs 400) is for wiggle room for ease of use + if (!api.assert(finalValueQuote && finalValueQuote >= 400, 'stable token pool USD value must be at least 500')) { + return false; + } + + calcResultParentPool = await calcParentPool(tokenNameBase, hasEnoughMarketPool[0], stablePrice, parentPairParams.precision); + } else if (quoteOrBase && quoteOrBase === 'quote') { + const stableTPrice = hasEnoughStablePool[0].quotePrice; + const quoteQuant = hasEnoughStablePool[0].quoteQuantity; + const tokenNameQuote = hasEnoughStablePool[0].tokenPair.split(':')[0]; + const stableUSDValue = api.BigNumber(stableTPrice).multipliedBy(quoteQuant).toFixed(parentPairParams.precision, api.BigNumber.ROUND_DOWN); + + // calulate the conservative value of the pool multiple the value of one side by 1.95, 1.95 used to ensure pools are not overvalued + const finalValueQuote = api.BigNumber(stableUSDValue).multipliedBy(1.95).toFixed(parentPairParams.precision, api.BigNumber.ROUND_DOWN); + + // users to be be informed of $500 barrier to entry/ the difference of $100 (500 vs 400) is for wiggle room for ease of use + if (!api.assert(finalValueQuote && finalValueQuote >= 400, 'stable token pool USD value must be at least 500')) { + return false; + } + calcResultParentPool = await calcParentPool(tokenNameQuote, hasEnoughMarketPool[0], stableTPrice, parentPairParams.precision); + } + + if (api.assert(calcResultParentPool && calcResultParentPool.poolValueUSD >= 400, 'parent token and XXX.D token pool USD value must be at least 500')) { + const feePercentage = api.BigNumber(parentPairParams.feePercentage); + let fee = '0'; + let finalQty = qtyAsBigNum; + + if (feePercentage.gt(0)) { + fee = qtyAsBigNum.multipliedBy(feePercentage).toFixed(parentPairParams.precision, api.BigNumber.ROUND_UP); + finalQty = qtyAsBigNum.minus(fee); + } + const xxxdToIssue = finalQty.multipliedBy(calcResultParentPool.parentPrice).toFixed(parentPairParams.precision, api.BigNumber.ROUND_DOWN); + + if (!api.assert(api.BigNumber(xxxdToIssue).gt(contractParams.minAmountConvertible), `resulting token issuance is too small; token price is ${calcResultParentPool.parentPrice}`)) { + return false; + } + const isBurnSuccess = await burnParentTokens(finalQty, fee, parentPairParams.parentSymbol, parentPairParams.burnRouting, contractParams, isSignedWithActiveKey); + + if (!api.assert(isBurnSuccess, 'error on token burn')) { + return false; + } + + await api.executeSmartContract('tokens', 'issue', { + to: api.sender, symbol: parentPairParams.symbol, quantity: xxxdToIssue, + }); + + const keyname = parentPairParams.parentSymbol; + + api.emit('Converted token to dollar token', { + symbol: parentPairParams.symbol, fee, feeRouting: parentPairParams.burnRouting, parentSymbol: keyname, precision: parentPairParams.precision, childIssued: xxxdToIssue, parentPriceInUSD: calcResultParentPool.parentPrice, + }); + } + } + } + } +}; diff --git a/contracts/burndollar_minify.js b/contracts/burndollar_minify.js new file mode 100644 index 00000000..c3ac0d87 --- /dev/null +++ b/contracts/burndollar_minify.js @@ -0,0 +1 @@ +const stablePairArray=["SWAP.HBD","SWAP.USDT","SWAP.DAI","SWAP.USDC"],countDecimals=value=>api.BigNumber(value).dp(),verifyTokenCreation=async symbolFind=>!!await api.db.findOneInTable("tokens","tokens",{symbol:symbolFind}),verifyTokenBalance=async(account,amount,symbolFind)=>{const findTokenBalance=await api.db.findOneInTable("tokens","balances",{account:account,symbol:symbolFind});return!(!findTokenBalance||!api.BigNumber(findTokenBalance.balance).gte(amount))},checkStablePosition=tokenPair=>{const[firstToken,secondToken]=tokenPair.split(":");return stablePairArray.includes(firstToken)?"base":!!stablePairArray.includes(secondToken)&&"quote"},findMarketPools=async(parentSymbol,toggle)=>{const childSymbol=`${parentSymbol}.D`;let poolData;if("stable"===toggle){const stableResults=[`${parentSymbol}`,`${childSymbol}`].flatMap((pElement=>stablePairArray.flatMap((sElement=>[`${sElement}:${pElement}`,`${pElement}:${sElement}`])))),multiPoolData=await api.db.findInTable("marketpools","pools",{tokenPair:{$in:stableResults}});poolData=multiPoolData.length>0?[multiPoolData.reduce(((max,item)=>item.baseQuantity+item.quoteQuantity>max.baseQuantity+max.quoteQuantity?item:max),multiPoolData[0])]:[]}if("market"===toggle){const marketParentArray=[`${parentSymbol}:${childSymbol}`,`${childSymbol}:${parentSymbol}`];poolData=await api.db.findInTable("marketpools","pools",{tokenPair:{$in:marketParentArray}})}const validPools=poolData.map((pool=>({tokenPair:pool.tokenPair,basePrice:pool.basePrice||"0",quotePrice:pool.quotePrice||"0",baseQuantity:pool.baseQuantity||"0",quoteQuantity:pool.quoteQuantity||"0"})));return validPools.length>0?validPools:null},calcParentPool=async(name,pool,tokenPriceUSD,precision)=>{const[firstToken,secondToken]=pool.tokenPair.split(":");let quoteOrBasePosition,otherTokenPriceUSD,halfPoolinUSD,fullPoolinUSD,parentTokenPrice,returnObject={};return name.includes(firstToken)&&(quoteOrBasePosition="base"),name.includes(secondToken)&&(quoteOrBasePosition="quote"),quoteOrBasePosition&&"base"===quoteOrBasePosition?(otherTokenPriceUSD=api.BigNumber(pool.quotePrice).multipliedBy(tokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN),parentTokenPrice=name.includes(".D")?api.BigNumber(tokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN):api.BigNumber(otherTokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN),halfPoolinUSD=api.BigNumber(otherTokenPriceUSD).multipliedBy(pool.baseQuantity).toFixed(precision,api.BigNumber.ROUND_DOWN),fullPoolinUSD=api.BigNumber(halfPoolinUSD).multipliedBy(1.95).toFixed(precision,api.BigNumber.ROUND_DOWN),returnObject={quoteToken:firstToken,quotePriceUSD:otherTokenPriceUSD,baseToken:secondToken,basePriceUSD:tokenPriceUSD,precision:precision,poolValueUSD:fullPoolinUSD,parentPrice:parentTokenPrice}):quoteOrBasePosition&&"quote"===quoteOrBasePosition&&(otherTokenPriceUSD=api.BigNumber(pool.basePrice).multipliedBy(tokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN),parentTokenPrice=name.includes(".D")?api.BigNumber(otherTokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN):api.BigNumber(tokenPriceUSD).toFixed(precision,api.BigNumber.ROUND_DOWN),halfPoolinUSD=api.BigNumber(otherTokenPriceUSD).multipliedBy(pool.quoteQuantity).toFixed(precision,api.BigNumber.ROUND_DOWN),fullPoolinUSD=api.BigNumber(halfPoolinUSD).multipliedBy(1.95).toFixed(precision,api.BigNumber.ROUND_DOWN),returnObject={quoteToken:firstToken,quotePriceUSD:tokenPriceUSD,baseToken:secondToken,basePriceUSD:otherTokenPriceUSD,precision:precision,poolValueUSD:fullPoolinUSD,parentPrice:parentTokenPrice}),returnObject},isTokenTransferVerified=(result,from,to,symbol,quantity,eventStr)=>!(void 0!==result.errors||!result.events||void 0===result.events.find((el=>("tokens"===el.contract||"burndollar"===el.contract)&&el.event===eventStr&&el.data.from===from&&el.data.to===to&&api.BigNumber(el.data.quantity).eq(quantity)&&el.data.symbol===symbol))),burnParentTokens=async(amount,fee,burnSymbol,toAccount,beedParams,isSignedWithActiveKey)=>{if(api.BigNumber(fee).gt(0)){const res=await api.executeSmartContract("tokens","transfer",{to:toAccount,symbol:burnSymbol,quantity:fee,isSignedWithActiveKey:isSignedWithActiveKey});if(!isTokenTransferVerified(res,api.sender,toAccount,burnSymbol,fee,"transfer"))return!1}const res2=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:burnSymbol,quantity:amount,isSignedWithActiveKey:isSignedWithActiveKey}),res3=await api.executeSmartContract("tokens","transfer",{to:"null",symbol:beedParams.burnToken,quantity:beedParams.burnUsageFee,isSignedWithActiveKey:isSignedWithActiveKey});return!!isTokenTransferVerified(res2,api.sender,"null",burnSymbol,amount,"transfer")&&!!isTokenTransferVerified(res3,api.sender,"null",beedParams.burnToken,beedParams.burnUsageFee,"transfer")};actions.createSSC=async()=>{if(!1===await api.db.tableExists("params")){await api.db.createTable("params"),await api.db.createTable("burnpair",["symbol","parentSymbol"]);const params={issueDTokenFee:"1000",updateParamsFee:"100",burnUsageFee:"1",minAmountConvertible:"1",dTokenToIssuer:"1000",burnToken:"BEED"};await api.db.insert("params",params)}},actions.updateParams=async payload=>{if(api.sender!==api.owner)return;const{issueDTokenFee:issueDTokenFee,updateParamsFee:updateParamsFee,burnUsageFee:burnUsageFee,minAmountConvertible:minAmountConvertible,dTokenToIssuer:dTokenToIssuer,burnToken:burnToken}=payload,params=await api.db.findOne("params",{});if(issueDTokenFee&&"string"==typeof issueDTokenFee&&!api.BigNumber(issueDTokenFee).isNaN()&&api.BigNumber(issueDTokenFee).gte(1)&&(params.issueDTokenFee=issueDTokenFee),updateParamsFee&&"string"==typeof updateParamsFee&&!api.BigNumber(updateParamsFee).isNaN()&&api.BigNumber(updateParamsFee).gte(1)&&(params.updateParamsFee=updateParamsFee),burnUsageFee&&"string"==typeof burnUsageFee&&!api.BigNumber(burnUsageFee).isNaN()&&api.BigNumber(burnUsageFee).gte(1)&&(params.burnUsageFee=burnUsageFee),minAmountConvertible&&"string"==typeof minAmountConvertible&&!minAmountConvertible.isNaN()&&minAmountConvertible.gte(1)&&(params.minAmountConvertible=minAmountConvertible),dTokenToIssuer&&"string"==typeof dTokenToIssuer&&!api.BigNumber(dTokenToIssuer).isNaN()&&api.BigNumber(dTokenToIssuer).gte(1)&&(params.dTokenToIssuer=dTokenToIssuer),burnToken&&"string"==typeof burnToken){await api.db.findOneInTable("tokens","tokens",{symbol:burnToken})&&(params.burnToken=burnToken)}await api.db.update("params",params)},actions.createTokenD=async payload=>{const{symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey,burnRouting:burnRouting,feePercentage:feePercentage}=payload,burnPairParams={},params=await api.db.findOne("params",{}),{issueDTokenFee:issueDTokenFee}=params,beedTokenBalance=await api.db.findOneInTable("tokens","balances",{account:api.sender,symbol:params.burnToken}),authorizedCreation=beedTokenBalance&&api.BigNumber(beedTokenBalance.balance).gte(issueDTokenFee);if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(authorizedCreation,"you must have enough BEED tokens cover the creation fees")&&api.assert(symbol&&"string"==typeof symbol&&symbol.length<=8&&symbol.length>0&&!symbol.includes(".D"),"symbol must be string of length 8 or less to create a xxx.D token")){const tokenParent=await api.db.findOneInTable("tokens","tokens",{symbol:symbol}),finalRouting=void 0===burnRouting?"null":burnRouting;if(api.assert(tokenParent.issuer===api.sender,"You must be the token issuer in order to issue D token")&&api.assert(api.isValidAccountName(finalRouting),"burn routing must be a valid Hive account name")&&api.assert(feePercentage&&"string"==typeof feePercentage&&!api.BigNumber(feePercentage).isNaN()&&api.BigNumber(feePercentage).gte(0)&&api.BigNumber(feePercentage).lte(1)&&countDecimals(feePercentage)<=4,"fee percentage must be between 0 and 1 / 0% and 100%")){let finalName="",dSymbol="";dSymbol=`${symbol}.D`;const tokenDExists=await api.db.findOneInTable("tokens","tokens",{symbol:dSymbol});if(api.assert(null===tokenDExists,"D token must not already exist")&&api.assert(tokenParent.precision>0&&Number.isInteger(tokenParent.precision),"invalid precision")){finalName=`${symbol} stablecoin`;const newToken={symbol:dSymbol,name:finalName,precision:tokenParent.precision,maxSupply:`${Number.MAX_SAFE_INTEGER}`};await api.executeSmartContract("tokens","create",newToken);const tokenCreated=await verifyTokenCreation(dSymbol);if(!api.assert(tokenCreated,"Token creation failed"))return!1;burnPairParams.issuer=api.sender,burnPairParams.symbol=dSymbol,burnPairParams.precision=tokenParent.precision,burnPairParams.parentSymbol=symbol,burnPairParams.burnRouting=finalRouting,burnPairParams.feePercentage=feePercentage,await api.db.insert("burnpair",burnPairParams),await api.executeSmartContract("tokens","issue",{to:api.sender,symbol:dSymbol,quantity:params.dTokenToIssuer}),api.BigNumber(issueDTokenFee).gt(0)&&await api.executeSmartContract("tokens","transfer",{to:"null",symbol:params.burnToken,quantity:issueDTokenFee,isSignedWithActiveKey:isSignedWithActiveKey}),api.emit("issued new token dollar stablecoin",{convertPercentage:feePercentage,feeRouting:burnPairParams.burnRouting,dSymbol:dSymbol})}}}},actions.updateBurnPair=async payload=>{const{symbol:symbol,burnRouting:burnRouting,feePercentage:feePercentage,isSignedWithActiveKey:isSignedWithActiveKey}=payload,finalRouting=void 0===burnRouting?"null":burnRouting;if(api.assert(api.isValidAccountName(finalRouting),"account for burn routing must exist")&&api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol,"symbol must be string")&&api.assert(finalRouting&&"string"==typeof finalRouting,"finalRouting must be string or null")&&api.assert(feePercentage&&"string"==typeof feePercentage&&!api.BigNumber(feePercentage).isNaN()&&api.BigNumber(feePercentage).gte(0)&&api.BigNumber(feePercentage).lte(1)&&countDecimals(feePercentage)<=4,"fee percentage must be between 0 and 1 / 0% and 100%")){const token=await api.db.findOne("burnpair",{symbol:symbol});if(!api.assert(null!=token,"D token must exist"))return!1;if(token&&api.assert(token.issuer===api.sender,"must be the issuer")){const params=await api.db.findOne("params",{}),{updateParamsFee:updateParamsFee}=params,beedTokenBalance=await api.db.findOneInTable("tokens","balances",{account:api.sender,symbol:params.burnToken}),authorizedCreation=beedTokenBalance&&api.BigNumber(beedTokenBalance.balance).gte(updateParamsFee);api.assert(authorizedCreation,"you must have enough BEED tokens to cover the update properties fee")&&(token.burnRouting=finalRouting,token.feePercentage=feePercentage,await api.db.update("burnpair",token),api.BigNumber(updateParamsFee).gt(0)&&await api.executeSmartContract("tokens","transfer",{to:"null",symbol:params.burnToken,quantity:updateParamsFee,isSignedWithActiveKey:isSignedWithActiveKey}),api.emit("updated params",{symbol:symbol,burnRouting:burnRouting,feePercentage:feePercentage}))}}},actions.convert=async payload=>{const{symbol:symbol,quantity:quantity,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params quantity")&&api.assert(symbol&&"string"==typeof symbol&&symbol.length>0&&symbol.length<=10,"symbol must be string")){const contractParams=await api.db.findOne("params",{}),parentPairParams=await api.db.findOne("burnpair",{parentSymbol:symbol}),qtyAsBigNum=api.BigNumber(quantity);if(api.assert(parentPairParams,"parent symbol must have a child .D token")&&api.assert(countDecimals(quantity)<=parentPairParams.precision,"symbol precision mismatch")&&api.assert(qtyAsBigNum.gte(contractParams.minAmountConvertible),"amount to convert must be >= 1")){const hasEnoughUtilityToken=await verifyTokenBalance(api.sender,contractParams.burnUsageFee,contractParams.burnToken),hasEnoughParentBalance=await verifyTokenBalance(api.sender,qtyAsBigNum,symbol),hasEnoughStablePool=await findMarketPools(symbol,"stable"),hasEnoughMarketPool=await findMarketPools(symbol,"market");if(api.assert(hasEnoughParentBalance,"not enough token balance")&&api.assert(hasEnoughUtilityToken,"not enough utility tokens")&&api.assert(hasEnoughStablePool,"token must be in pool with a stable coin")&&api.assert(hasEnoughMarketPool,"token must be in pool with xxx.d token")){const quoteOrBase=checkStablePosition(hasEnoughStablePool[0].tokenPair);let calcResultParentPool;if(quoteOrBase&&"base"===quoteOrBase){const stablePrice=hasEnoughStablePool[0].basePrice,stableQuant=hasEnoughStablePool[0].baseQuantity,tokenNameBase=hasEnoughStablePool[0].tokenPair.split(":")[1],stableUSDValue=api.BigNumber(stablePrice).multipliedBy(stableQuant).toFixed(parentPairParams.precision,api.BigNumber.ROUND_DOWN),finalValueQuote=api.BigNumber(stableUSDValue).multipliedBy(1.95).toFixed(parentPairParams.precision,api.BigNumber.ROUND_DOWN);if(!api.assert(finalValueQuote&&finalValueQuote>=400,"stable token pool USD value must be at least 500"))return!1;calcResultParentPool=await calcParentPool(tokenNameBase,hasEnoughMarketPool[0],stablePrice,parentPairParams.precision)}else if(quoteOrBase&&"quote"===quoteOrBase){const stableTPrice=hasEnoughStablePool[0].quotePrice,quoteQuant=hasEnoughStablePool[0].quoteQuantity,tokenNameQuote=hasEnoughStablePool[0].tokenPair.split(":")[0],stableUSDValue=api.BigNumber(stableTPrice).multipliedBy(quoteQuant).toFixed(parentPairParams.precision,api.BigNumber.ROUND_DOWN),finalValueQuote=api.BigNumber(stableUSDValue).multipliedBy(1.95).toFixed(parentPairParams.precision,api.BigNumber.ROUND_DOWN);if(!api.assert(finalValueQuote&&finalValueQuote>=400,"stable token pool USD value must be at least 500"))return!1;calcResultParentPool=await calcParentPool(tokenNameQuote,hasEnoughMarketPool[0],stableTPrice,parentPairParams.precision)}if(api.assert(calcResultParentPool&&calcResultParentPool.poolValueUSD>=400,"parent token and XXX.D token pool USD value must be at least 500")){const feePercentage=api.BigNumber(parentPairParams.feePercentage);let fee="0",finalQty=qtyAsBigNum;feePercentage.gt(0)&&(fee=qtyAsBigNum.multipliedBy(feePercentage).toFixed(parentPairParams.precision,api.BigNumber.ROUND_UP),finalQty=qtyAsBigNum.minus(fee));const xxxdToIssue=finalQty.multipliedBy(calcResultParentPool.parentPrice).toFixed(parentPairParams.precision,api.BigNumber.ROUND_DOWN);if(!api.assert(api.BigNumber(xxxdToIssue).gt(contractParams.minAmountConvertible),`resulting token issuance is too small; token price is ${calcResultParentPool.parentPrice}`))return!1;const isBurnSuccess=await burnParentTokens(finalQty,fee,parentPairParams.parentSymbol,parentPairParams.burnRouting,contractParams,isSignedWithActiveKey);if(!api.assert(isBurnSuccess,"error on token burn"))return!1;await api.executeSmartContract("tokens","issue",{to:api.sender,symbol:parentPairParams.symbol,quantity:xxxdToIssue});const keyname=parentPairParams.parentSymbol;api.emit("Converted token to dollar token",{symbol:parentPairParams.symbol,fee:fee,feeRouting:parentPairParams.burnRouting,parentSymbol:keyname,precision:parentPairParams.precision,childIssued:xxxdToIssue,parentPriceInUSD:calcResultParentPool.parentPrice})}}}}}; \ No newline at end of file diff --git a/contracts/tokens.js b/contracts/tokens.js index 41999572..d0eb50ee 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -51,6 +51,7 @@ const VERIFIED_ISSUERS = [ 'mining', 'tokenfunds', 'beedollar', + 'burndollar', ]; const calculateBalance = (balance, quantity, precision, add) => (add @@ -354,8 +355,7 @@ actions.create = async (payload) => { const params = await api.db.findOne('params', {}); const { tokenCreationFee, heAccounts } = params; - const fromVerifiedContract = (api.sender === 'hive-engine' - && callingContractInfo + const fromVerifiedContract = (callingContractInfo && VERIFIED_ISSUERS.indexOf(callingContractInfo.name) !== -1); // get api.sender's UTILITY_TOKEN_SYMBOL balance @@ -390,7 +390,7 @@ actions.create = async (payload) => { && api.assert(heAccounts[api.sender] === 1 || symbol.indexOf('SWAP') === -1, 'invalid symbol: not allowed to use SWAP') && api.assert(heAccounts[api.sender] === 1 || symbol.indexOf('ETH') === -1, 'invalid symbol: not allowed to use ETH') && api.assert(heAccounts[api.sender] === 1 || symbol.indexOf('BSC') === -1, 'invalid symbol: not allowed to use BSC') - && api.assert(heAccounts[api.sender] === 1 || symbol.indexOf('.') === -1, 'invalid symbol: usage of "." is restricted') + && api.assert(heAccounts[api.sender] === 1 || symbol.indexOf('.') === -1 || (callingContractInfo && callingContractInfo.name === 'burndollar'), 'invalid symbol: usage of "." is restricted') && api.assert(api.validator.isAlphanumeric(api.validator.blacklist(name, ' ')) && name.length > 0 && name.length <= 50, 'invalid name: letters, numbers, whitespaces only, max length of 50') && api.assert(url === undefined || url.length <= 255, 'invalid url: max length of 255') && api.assert((precision >= 0 && precision <= 8) && (Number.isInteger(precision)), 'invalid precision') @@ -444,7 +444,8 @@ actions.issue = async (payload) => { const fromVerifiedContract = (api.sender === 'null' && VERIFIED_ISSUERS.indexOf(callingContractInfo.name) !== -1) - || (callingContractInfo && callingContractInfo.name === 'beedollar'); + || (callingContractInfo && callingContractInfo.name === 'beedollar') + || (callingContractInfo && callingContractInfo.name === 'burndollar'); if (fromVerifiedContract || (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') @@ -573,7 +574,7 @@ actions.transfer = async (payload) => { if (api.assert(token !== null, 'symbol does not exist') && api.assert(countDecimals(quantity) <= token.precision, 'symbol precision mismatch') && api.assert(api.BigNumber(quantity).gt(0), 'must transfer positive quantity')) { - //quantity = api.BigNumber(quantity).toFixed(token.precision); + // quantity = api.BigNumber(quantity).toFixed(token.precision); if (await subBalance(api.sender, token, quantity, 'balances')) { const res = await addBalance(finalTo, token, quantity, 'balances'); diff --git a/contracts/tokens_minify.js b/contracts/tokens_minify.js index 740db9db..0ae86317 100644 --- a/contracts/tokens_minify.js +++ b/contracts/tokens_minify.js @@ -1 +1 @@ -const ACCOUNT_BLACKLIST={gateiodeposit:1,deepcrypto8:1,bittrex:1,poloniex:1,"huobi-pro":1,"binance-hot":1,bitvavo:1,blocktrades:1,probitsteem:1,probithive:1,ionomy:1,mxchive:1,coinbasebase:1,orinoco:1,"user.dunamu":1},HE_ACCOUNTS={"hive-engine":1,"swap-eth":1,"btc-swap":1,"graphene-swap":1,"honey-swap":1},RESERVED_SYMBOLS={ENG:"null",STEEMP:"steem-peg",BTCP:"btcpeg",LTCP:"ltcp",DOGEP:"dogep",BCHP:"bchp",SMTT:"steemmonsters",EM:"steem-eng",EMFOUR:"steem-eng",HIVEP:"steem-tokens",GLINT:"steemmonsters"},VERIFIED_ISSUERS=["comments","mining","tokenfunds","beedollar"],calculateBalance=(balance,quantity,precision,add)=>add?api.BigNumber(balance).plus(quantity).toFixed(precision):api.BigNumber(balance).minus(quantity).toFixed(precision),countDecimals=value=>api.BigNumber(value).dp(),findAndProcessAll=async(table,query,callback)=>{let offset=0,results=[],done=!1;for(;!done;)if(results=await api.db.find(table,query,1e3,offset),results){for(let i=0;i{if(!1===await api.db.tableExists("tokens")){await api.db.createTable("tokens",["symbol"]),await api.db.createTable("balances",["account"]),await api.db.createTable("contractsBalances",["account"]),await api.db.createTable("params"),await api.db.createTable("pendingUnstakes",["account","unstakeCompleteTimestamp"]),await api.db.createTable("delegations",["from","to"]),await api.db.createTable("pendingUndelegations",["account","completeTimestamp"]);const params={tokenCreationFee:"0",enableDelegationFee:"0",enableStakingFee:"0"};await api.db.insert("params",params)}else{const params=await api.db.findOne("params",{});if(!params.blacklist){params.blacklist=ACCOUNT_BLACKLIST,params.heAccounts=HE_ACCOUNTS;const unsets={};let useUnsets=!1;params.fixMultiTxUnstakeBalance&&(delete params.fixMultiTxUnstakeBalance,unsets.fixMultiTxUnstakeBalance="",useUnsets=!0),params.cancelBadUnstakes&&(delete params.cancelBadUnstakes,unsets.cancelBadUnstakes="",useUnsets=!0),useUnsets?await api.db.update("params",params,unsets):await api.db.update("params",params)}}};const balanceTemplate={account:null,symbol:null,balance:"0",stake:"0",pendingUnstake:"0",delegationsIn:"0",delegationsOut:"0",pendingUndelegations:"0"},addStake=async(account,token,quantity)=>{let balance=await api.db.findOne("balances",{account:account,symbol:token.symbol});null===balance&&(balance=balanceTemplate,balance.account=account,balance.symbol=token.symbol,balance=await api.db.insert("balances",balance)),void 0===balance.stake&&(balance.stake="0",balance.pendingUnstake="0");const originalStake=balance.stake;return balance.stake=calculateBalance(balance.stake,quantity,token.precision,!0),!!api.assert(api.BigNumber(balance.stake).gt(originalStake),"cannot add")&&(await api.db.update("balances",balance),void 0===token.totalStaked&&(token.totalStaked="0"),token.totalStaked=calculateBalance(token.totalStaked,quantity,token.precision,!0),await api.db.update("tokens",token),!0)},subBalance=async(account,token,quantity,table)=>{const balance=await api.db.findOne(table,{account:account,symbol:token.symbol});if(api.assert(null!==balance,"balance does not exist")&&api.assert(api.BigNumber(balance.balance).gte(quantity),"overdrawn balance")){const originalBalance=balance.balance;if(balance.balance=calculateBalance(balance.balance,quantity,token.precision,!1),api.assert(api.BigNumber(balance.balance).lt(originalBalance),"cannot subtract"))return await api.db.update(table,balance),!0}return!1},addBalance=async(account,token,quantity,table)=>{let balance=await api.db.findOne(table,{account:account,symbol:token.symbol});if(null===balance)return balance=balanceTemplate,balance.account=account,balance.symbol=token.symbol,balance.balance=quantity,await api.db.insert(table,balance),!0;const originalBalance=balance.balance;return balance.balance=calculateBalance(balance.balance,quantity,token.precision,!0),!!api.assert(api.BigNumber(balance.balance).gt(originalBalance),"cannot add")&&(await api.db.update(table,balance),!0)};actions.updateParams=async payload=>{if(api.sender!==api.owner)return;const{tokenCreationFee:tokenCreationFee,enableDelegationFee:enableDelegationFee,enableStakingFee:enableStakingFee,blacklist:blacklist,heAccounts:heAccounts}=payload,params=await api.db.findOne("params",{});tokenCreationFee&&"string"==typeof tokenCreationFee&&!api.BigNumber(tokenCreationFee).isNaN()&&api.BigNumber(tokenCreationFee).gte(0)&&(params.tokenCreationFee=tokenCreationFee),enableDelegationFee&&"string"==typeof enableDelegationFee&&!api.BigNumber(enableDelegationFee).isNaN()&&api.BigNumber(enableDelegationFee).gte(0)&&(params.enableDelegationFee=enableDelegationFee),enableStakingFee&&"string"==typeof enableStakingFee&&!api.BigNumber(enableStakingFee).isNaN()&&api.BigNumber(enableStakingFee).gte(0)&&(params.enableStakingFee=enableStakingFee),blacklist&&"object"==typeof blacklist&&(params.blacklist=blacklist),heAccounts&&"object"==typeof heAccounts&&(params.heAccounts=heAccounts),await api.db.update("params",params)},actions.updateUrl=async payload=>{const{url:url,symbol:symbol}=payload;if(api.assert(symbol&&"string"==typeof symbol&&url&&"string"==typeof url,"invalid params")&&api.assert(url.length<=255,"invalid url: max length of 255")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(token.issuer===api.sender,"must be the issuer"))try{const metadata=JSON.parse(token.metadata);api.assert(metadata&&metadata.url,"an error occured when trying to update the url")&&(metadata.url=url,token.metadata=JSON.stringify(metadata),await api.db.update("tokens",token))}catch(e){}}},actions.updateMetadata=async payload=>{const{metadata:metadata,symbol:symbol,callingContractInfo:callingContractInfo}=payload,fromVerifiedContract="hive-engine"===api.sender&&callingContractInfo&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name);if(api.assert(symbol&&"string"==typeof symbol&&metadata&&"object"==typeof metadata,"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(fromVerifiedContract||token.issuer===api.sender,"must be the issuer"))try{const finalMetadata=JSON.stringify(metadata);api.assert(finalMetadata.length<=1e3,"invalid metadata: max length of 1000")&&(token.metadata=finalMetadata,await api.db.update("tokens",token))}catch(e){}}},actions.updatePrecision=async payload=>{const{symbol:symbol,precision:precision,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol)&&api.assert(precision>0&&precision<=8&&Number.isInteger(precision),"invalid precision")){const token=await api.db.findOne("tokens",{symbol:symbol});token&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(precision>token.precision,"precision can only be increased")&&(token.precision=precision,await api.db.update("tokens",token))}},actions.transferOwnership=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to,"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(token.issuer===api.sender,"must be the issuer")){const finalTo=to.trim();api.assert(api.isValidAccountName(finalTo),"invalid to")&&(token.issuer=finalTo,await api.db.update("tokens",token),api.emit("transferOwnership",{from:api.sender,to:finalTo,symbol:symbol}))}}},actions.create=async payload=>{const{name:name,symbol:symbol,url:url,precision:precision,maxSupply:maxSupply,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload,params=await api.db.findOne("params",{}),{tokenCreationFee:tokenCreationFee,heAccounts:heAccounts}=params,fromVerifiedContract="hive-engine"===api.sender&&callingContractInfo&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name),utilityTokenBalance=fromVerifiedContract?null:await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),authorizedCreation=!(!fromVerifiedContract&&!api.BigNumber(tokenCreationFee).lte(0)&&1!==heAccounts[api.sender])||utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(tokenCreationFee);if(api.assert(authorizedCreation,"you must have enough tokens to cover the creation fees")&&api.assert(fromVerifiedContract||!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(name&&"string"==typeof name&&symbol&&"string"==typeof symbol&&(void 0===url||url&&"string"==typeof url)&&(precision&&"number"==typeof precision||0===precision)&&maxSupply&&"string"==typeof maxSupply&&!api.BigNumber(maxSupply).isNaN(),"invalid params")&&api.assert(symbol.length>0&&symbol.length<=10&&api.validator.isAlpha(api.validator.blacklist(symbol,"."))&&api.validator.isUppercase(symbol)&&(-1===symbol.indexOf(".")||symbol.indexOf(".")>0&&symbol.indexOf(".")0&&name.length<=50,"invalid name: letters, numbers, whitespaces only, max length of 50")&&api.assert(void 0===url||url.length<=255,"invalid url: max length of 255")&&api.assert(precision>=0&&precision<=8&&Number.isInteger(precision),"invalid precision")&&api.assert(api.BigNumber(maxSupply).gt(0),"maxSupply must be positive")&&api.assert(api.BigNumber(maxSupply).lte(Number.MAX_SAFE_INTEGER),`maxSupply must be lower than ${Number.MAX_SAFE_INTEGER}`)){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null===token,"symbol already exists")){let metadata={url:void 0===url?"":url};metadata=JSON.stringify(metadata);const newToken={issuer:fromVerifiedContract?"null":api.sender,symbol:symbol,name:name,metadata:metadata,precision:precision,maxSupply:api.BigNumber(maxSupply).toFixed(precision),supply:"0",circulatingSupply:"0",stakingEnabled:!1,unstakingCooldown:1,delegationEnabled:!1,undelegationCooldown:0};await api.db.insert("tokens",newToken),api.BigNumber(tokenCreationFee).gt(0)&&void 0===heAccounts[api.sender]&&!fromVerifiedContract&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:tokenCreationFee,isSignedWithActiveKey:isSignedWithActiveKey})}}},actions.issue=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;const fromVerifiedContract="null"===api.sender&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name)||callingContractInfo&&"beedollar"===callingContractInfo.name;if(fromVerifiedContract||api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(fromVerifiedContract||token.issuer===api.sender,"not allowed to issue tokens")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must issue positive quantity")&&api.assert(api.BigNumber(token.maxSupply).minus(token.supply).gte(quantity),"quantity exceeds available supply")&&api.assert(api.isValidAccountName(finalTo),"invalid to")){quantity=api.BigNumber(quantity).toFixed(token.precision);let res=await addBalance(token.issuer,token,quantity,"balances");!0===res&&finalTo!==token.issuer&&await subBalance(token.issuer,token,quantity,"balances")&&(res=await addBalance(finalTo,token,quantity,"balances"),!1===res&&await addBalance(token.issuer,token,quantity,"balances")),!0===res&&(token.supply=calculateBalance(token.supply,quantity,token.precision,!0),"null"!==finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!0)),await api.db.update("tokens",token),api.emit("transferFromContract",{from:"tokens",to:finalTo,symbol:symbol,quantity:quantity}))}}},actions.issueToContract=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;const fromVerifiedContract="null"===api.sender&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name);if(fromVerifiedContract||api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(fromVerifiedContract||token.issuer===api.sender,"not allowed to issue tokens")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must issue positive quantity")&&api.assert(api.BigNumber(token.maxSupply).minus(token.supply).gte(quantity),"quantity exceeds available supply")&&api.assert(finalTo.length>=3&&finalTo.length<=50,"invalid to")){quantity=api.BigNumber(quantity).toFixed(token.precision);!0===await addBalance(finalTo,token,quantity,"contractsBalances")&&(token.supply=calculateBalance(token.supply,quantity,token.precision,!0),"null"!==finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!0)),await api.db.update("tokens",token),api.emit("issueToContract",{from:"tokens",to:finalTo,symbol:symbol,quantity:quantity}))}}},actions.transfer=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim();if(api.assert(finalTo!==api.sender,"cannot transfer to self")){const params=await api.db.findOne("params",{}),{blacklist:blacklist}=params;if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(void 0===blacklist[finalTo],`not allowed to send to ${finalTo}`)){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&await subBalance(api.sender,token,quantity,"balances")){return!1===await addBalance(finalTo,token,quantity,"balances")?(await addBalance(api.sender,token,quantity,"balances"),!1):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transfer",{from:api.sender,to:finalTo,symbol:symbol,quantity:quantity}),!0)}}}}return!1},actions.transferToContract=async payload=>{const{from:from,to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;const finalFrom=void 0===from||"null"!==api.sender?api.sender:from;if(api.assert(!0===isSignedWithActiveKey||"null"===api.sender,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim().toLowerCase();if(api.assert(finalTo!==finalFrom,"cannot transfer to self")&&api.assert(finalTo.length>=3&&finalTo.length<=50,"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(finalFrom,token,quantity,"balances"))){!1===await addBalance(finalTo,token,quantity,"contractsBalances")?await addBalance(finalFrom,token,quantity,"balances"):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transferToContract",{from:finalFrom,to:finalTo,symbol:symbol,quantity:quantity}))}}}},actions.transferFromContract=async payload=>{if(api.assert("null"===api.sender,"not authorized")){const{from:from,to:to,symbol:symbol,type:type}=payload;let quantity=payload.quantity;const types=["user","contract"];if(api.assert(to&&"string"==typeof to&&from&&"string"==typeof from&&symbol&&"string"==typeof symbol&&type&&types.includes(type)&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),table="user"===type?"balances":"contractsBalances";if(api.assert("user"===type||"contract"===type&&finalTo!==from,"cannot transfer to self")){const toValid="user"===type?api.isValidAccountName(finalTo):finalTo.length>=3&&finalTo.length<=50;if(api.assert(!0===toValid,"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(from,token,quantity,"contractsBalances"))){!1===await addBalance(finalTo,token,quantity,table)?await addBalance(from,token,quantity,"contractsBalances"):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transferFromContract",{from:from,to:finalTo,symbol:symbol,quantity:quantity}))}}}}}};const processUnstake=async unstake=>{const{account:account,symbol:symbol,quantity:quantity,quantityLeft:quantityLeft,numberTransactionsLeft:numberTransactionsLeft}=unstake,newUnstake=unstake,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});let tokensToRelease=0,nextTokensToRelease=0;if(api.assert(null!==balance,"balance does not exist")&&(1===numberTransactionsLeft?(tokensToRelease=quantityLeft,await api.db.remove("pendingUnstakes",unstake)):(tokensToRelease=api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN),newUnstake.quantityLeft=api.BigNumber(newUnstake.quantityLeft).minus(tokensToRelease).toFixed(token.precision),newUnstake.numberTransactionsLeft-=1,nextTokensToRelease=1===newUnstake.numberTransactionsLeft?newUnstake.quantityLeft:tokensToRelease,newUnstake.nextTransactionTimestamp=api.BigNumber(newUnstake.nextTransactionTimestamp).plus(newUnstake.millisecPerPeriod).toNumber(),await api.db.update("pendingUnstakes",newUnstake)),api.BigNumber(tokensToRelease).gt(0))){const originalBalance=balance.balance,originalPendingStake=balance.pendingUnstake;balance.balance=calculateBalance(balance.balance,tokensToRelease,token.precision,!0),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,tokensToRelease,token.precision,!1),api.assert(api.BigNumber(balance.pendingUnstake).lt(originalPendingStake)&&api.BigNumber(balance.balance).gt(originalBalance),"cannot subtract")&&(api.BigNumber(nextTokensToRelease).gt(0)&&(balance.stake=calculateBalance(balance.stake,nextTokensToRelease,token.precision,!1),token.totalStaked=calculateBalance(token.totalStaked,nextTokensToRelease,token.precision,!1),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()})),await api.db.update("balances",balance),await api.db.update("tokens",token),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}),api.emit("unstake",{account:account,symbol:symbol,quantity:tokensToRelease}))}};actions.checkPendingUnstakes=async()=>{if(api.assert("null"===api.sender,"not authorized")){const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let pendingUnstakes=await api.db.find("pendingUnstakes",{nextTransactionTimestamp:{$lte:timestamp}},1e3,0,[{index:"_id",descending:!1}]),nbPendingUnstakes=pendingUnstakes.length;for(;nbPendingUnstakes>0;){for(let index=0;index{const{symbol:symbol,unstakingCooldown:unstakingCooldown,numberTransactions:numberTransactions,isSignedWithActiveKey:isSignedWithActiveKey}=payload,params=await api.db.findOne("params",{}),{enableStakingFee:enableStakingFee}=params,utilityTokenBalance=await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),enoughFunds=utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(enableStakingFee),authorized=void 0===enableStakingFee||api.BigNumber(enableStakingFee).lte(0)||enoughFunds;if(api.assert(authorized,"you must have enough tokens to cover fees")&&api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol,"invalid symbol")&&api.assert(unstakingCooldown&&Number.isInteger(unstakingCooldown)&&unstakingCooldown>0&&unstakingCooldown<=18250,"unstakingCooldown must be an integer between 1 and 18250")&&api.assert(numberTransactions&&Number.isInteger(numberTransactions)&&numberTransactions>0&&numberTransactions<=18250,"numberTransactions must be an integer between 1 and 18250")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(void 0===token.stakingEnabled||!1===token.stakingEnabled,"staking already enabled")&&(token.stakingEnabled=!0,token.totalStaked="0",token.unstakingCooldown=unstakingCooldown,token.numberTransactions=numberTransactions,await api.db.update("tokens",token),api.BigNumber(enableStakingFee).gt(0)&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:enableStakingFee,isSignedWithActiveKey:isSignedWithActiveKey}))}},actions.stake=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol}),finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(api.BigNumber(quantity).gt(0),"must stake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(api.sender,token,quantity,"balances"))){!1===await addStake(finalTo,token,quantity)?await addBalance(api.sender,token,quantity,"balances"):(api.emit("stake",{account:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo}),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}},actions.stakeFromContract=async payload=>{const{symbol:symbol,to:to,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;if(callingContractInfo&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol}),finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(api.BigNumber(quantity).gt(0),"must stake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(callingContractInfo.name,token,quantity,"contractsBalances"))){!1===await addStake(finalTo,token,quantity)?await addBalance(callingContractInfo.name,token,quantity,"balances"):(api.emit("stakeFromContract",{account:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo}),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}};const validateAvailableStake=async(balance,token,quantity)=>{let availableStakeBalance=api.BigNumber(balance.stake);return await findAndProcessAll("pendingUnstakes",{symbol:balance.symbol,account:balance.account},(async pendingUnstake=>{if(pendingUnstake.numberTransactionsLeft>1){const tokensToRelease=api.BigNumber(pendingUnstake.quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN);availableStakeBalance=availableStakeBalance.minus(pendingUnstake.quantityLeft).plus(tokensToRelease)}})),api.assert(availableStakeBalance.gte(quantity),"overdrawn stake")},startUnstake=async(account,token,quantity)=>{const balance=await api.db.findOne("balances",{account:account,symbol:token.symbol});if(!api.assert(null!==balance,"balance does not exist")||!await validateAvailableStake(balance,token,quantity))return!1;{const originalStake=balance.stake,originalPendingStake=balance.pendingUnstake,nextTokensToRelease=token.numberTransactions>1?api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN):quantity;balance.stake=calculateBalance(balance.stake,nextTokensToRelease,token.precision,!1),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,quantity,token.precision,!0),api.assert(api.BigNumber(balance.stake).lt(originalStake)&&api.BigNumber(balance.pendingUnstake).gt(originalPendingStake),"cannot subtract")&&(await api.db.update("balances",balance),token.totalStaked=calculateBalance(token.totalStaked,nextTokensToRelease,token.precision,!1),await api.db.update("tokens",token),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===token.symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:token.symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}))}const blockDate=new Date(`${api.hiveBlockTimestamp}.000Z`),cooldownPeriodMillisec=24*token.unstakingCooldown*3600*1e3,millisecPerPeriod=api.BigNumber(cooldownPeriodMillisec).dividedBy(token.numberTransactions).integerValue(api.BigNumber.ROUND_DOWN),nextTransactionTimestamp=api.BigNumber(blockDate.getTime()).plus(millisecPerPeriod).toNumber(),unstake={account:account,symbol:token.symbol,quantity:quantity,quantityLeft:quantity,nextTransactionTimestamp:nextTransactionTimestamp,numberTransactionsLeft:token.numberTransactions,millisecPerPeriod:millisecPerPeriod,txID:api.transactionId};return await api.db.insert("pendingUnstakes",unstake),!0};actions.unstake=async payload=>{const{symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must unstake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await startUnstake(api.sender,token,quantity)&&api.emit("unstakeStart",{account:api.sender,symbol:symbol,quantity:quantity}))}};const processCancelUnstake=async unstake=>{const{account:account,symbol:symbol,quantity:quantity,quantityLeft:quantityLeft,numberTransactionsLeft:numberTransactionsLeft}=unstake,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==balance,"balance does not exist")&&api.assert(api.BigNumber(balance.pendingUnstake).gte(quantityLeft),"overdrawn pendingUnstake")){const originalStake=balance.stake,originalPendingStake=balance.pendingUnstake,tokensToRelease=numberTransactionsLeft>1?api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN):quantityLeft;if(balance.stake=calculateBalance(balance.stake,tokensToRelease,token.precision,!0),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,quantityLeft,token.precision,!1),api.assert(api.BigNumber(balance.pendingUnstake).lt(originalPendingStake)&&api.BigNumber(balance.stake).gt(originalStake),"cannot subtract"))return await api.db.update("balances",balance),token.totalStaked=calculateBalance(token.totalStaked,tokensToRelease,token.precision,!0),await api.db.update("tokens",token),api.emit("unstakeCancel",{account:account,symbol:symbol,quantity:quantityLeft}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:tokensToRelease}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}),!0}return!1};actions.cancelUnstake=async payload=>{const{txID:txID,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(txID&&"string"==typeof txID,"invalid params")){const unstake=await api.db.findOne("pendingUnstakes",{account:api.sender,txID:txID});api.assert(unstake,"unstake does not exist")&&await processCancelUnstake(unstake)&&await api.db.remove("pendingUnstakes",unstake)}},actions.enableDelegation=async payload=>{const{symbol:symbol,undelegationCooldown:undelegationCooldown,isSignedWithActiveKey:isSignedWithActiveKey}=payload,params=await api.db.findOne("params",{}),{enableDelegationFee:enableDelegationFee}=params,utilityTokenBalance=await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),enoughFunds=utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(enableDelegationFee),authorized=void 0===enableDelegationFee||api.BigNumber(enableDelegationFee).lte(0)||enoughFunds;if(api.assert(authorized,"you must have enough tokens to cover fees")&&api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol,"invalid symbol")&&api.assert(undelegationCooldown&&Number.isInteger(undelegationCooldown)&&undelegationCooldown>0&&undelegationCooldown<=18250,"undelegationCooldown must be an integer between 1 and 18250")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(void 0===token.delegationEnabled||!1===token.delegationEnabled,"delegation already enabled")&&(token.delegationEnabled=!0,token.undelegationCooldown=undelegationCooldown,await api.db.update("tokens",token),api.BigNumber(enableDelegationFee).gt(0)&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:enableDelegationFee,isSignedWithActiveKey:isSignedWithActiveKey}))}},actions.delegate=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.delegationEnabled,"delegation not enabled")&&api.assert(finalTo!==api.sender,"cannot delegate to yourself")&&api.assert(api.BigNumber(quantity).gt(0),"must delegate positive quantity")){quantity=api.BigNumber(quantity).toFixed(token.precision);const balanceFrom=await api.db.findOne("balances",{account:api.sender,symbol:symbol});if(api.assert(null!==balanceFrom,"balanceFrom does not exist")&&await validateAvailableStake(balanceFrom,token,quantity)){void 0===balanceFrom.stake?(balanceFrom.stake="0",balanceFrom.pendingUnstake="0",balanceFrom.delegationsIn="0",balanceFrom.delegationsOut="0",balanceFrom.pendingUndelegations="0"):void 0===balanceFrom.delegationsIn&&(balanceFrom.delegationsIn="0",balanceFrom.delegationsOut="0",balanceFrom.pendingUndelegations="0",balanceFrom.delegatedStake&&(delete balanceFrom.delegatedStake,delete balanceFrom.receivedStake));let balanceTo=await api.db.findOne("balances",{account:finalTo,symbol:symbol});null===balanceTo?(balanceTo=balanceTemplate,balanceTo.account=finalTo,balanceTo.symbol=symbol,balanceTo=await api.db.insert("balances",balanceTo)):void 0===balanceTo.stake?(balanceTo.stake="0",balanceTo.pendingUnstake="0",balanceTo.delegationsIn="0",balanceTo.delegationsOut="0",balanceTo.pendingUndelegations="0"):void 0===balanceTo.delegationsIn&&(balanceTo.delegationsIn="0",balanceTo.delegationsOut="0",balanceTo.pendingUndelegations="0",balanceTo.delegatedStake&&(delete balanceTo.delegatedStake,delete balanceTo.receivedStake));let delegation=await api.db.findOne("delegations",{to:finalTo,from:api.sender,symbol:symbol});const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();null==delegation?(balanceFrom.stake=calculateBalance(balanceFrom.stake,quantity,token.precision,!1),balanceFrom.delegationsOut=calculateBalance(balanceFrom.delegationsOut,quantity,token.precision,!0),await api.db.update("balances",balanceFrom),balanceTo.delegationsIn=calculateBalance(balanceTo.delegationsIn,quantity,token.precision,!0),await api.db.update("balances",balanceTo),delegation={},delegation.from=api.sender,delegation.to=finalTo,delegation.symbol=symbol,delegation.quantity=quantity,delegation.created=timestamp,delegation.updated=timestamp,await api.db.insert("delegations",delegation),api.emit("delegate",{to:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&(await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:api.sender}),await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo})),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity,delegated:!0}),await api.executeSmartContract("mining","handleStakeChange",{account:api.sender,symbol:symbol,quantity:api.BigNumber(quantity).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:api.sender,token:token}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token})):(balanceFrom.stake=calculateBalance(balanceFrom.stake,quantity,token.precision,!1),balanceFrom.delegationsOut=calculateBalance(balanceFrom.delegationsOut,quantity,token.precision,!0),await api.db.update("balances",balanceFrom),balanceTo.delegationsIn=calculateBalance(balanceTo.delegationsIn,quantity,token.precision,!0),await api.db.update("balances",balanceTo),delegation.quantity=calculateBalance(delegation.quantity,quantity,token.precision,!0),delegation.updated=timestamp,await api.db.update("delegations",delegation),api.emit("delegate",{to:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&(await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:api.sender}),await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo})),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity,delegated:!0}),await api.executeSmartContract("mining","handleStakeChange",{account:api.sender,symbol:symbol,quantity:api.BigNumber(quantity).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:api.sender,token:token}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}}}},actions.undelegate=async payload=>{const{symbol:symbol,from:from,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&from&&"string"==typeof from&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalFrom=from.trim();if(api.assert(finalFrom.length>=3&&finalFrom.length<=16,"invalid from")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.delegationEnabled,"delegation not enabled")&&api.assert(finalFrom!==api.sender,"cannot undelegate from yourself")&&api.assert(api.BigNumber(quantity).gt(0),"must undelegate positive quantity")){quantity=api.BigNumber(quantity).toFixed(token.precision);const balanceTo=await api.db.findOne("balances",{account:api.sender,symbol:symbol});if(api.assert(null!==balanceTo,"balanceTo does not exist")&&api.assert(api.BigNumber(balanceTo.delegationsOut).gte(quantity),"overdrawn delegation")){const balanceFrom=await api.db.findOne("balances",{account:finalFrom,symbol:symbol});if(api.assert(null!==balanceFrom,"balanceFrom does not exist")){const delegation=await api.db.findOne("delegations",{to:finalFrom,from:api.sender,symbol:symbol});if(api.assert(null!==delegation,"delegation does not exist")&&api.assert(api.BigNumber(delegation.quantity).gte(quantity),"overdrawn delegation")){balanceTo.pendingUndelegations=calculateBalance(balanceTo.pendingUndelegations,quantity,token.precision,!0),balanceTo.delegationsOut=calculateBalance(balanceTo.delegationsOut,quantity,token.precision,!1),await api.db.update("balances",balanceTo),balanceFrom.delegationsIn=calculateBalance(balanceFrom.delegationsIn,quantity,token.precision,!1),await api.db.update("balances",balanceFrom),delegation.quantity=calculateBalance(delegation.quantity,quantity,token.precision,!1),api.BigNumber(delegation.quantity).gt(0)?await api.db.update("delegations",delegation):await api.db.remove("delegations",delegation);const blockDate=new Date(`${api.hiveBlockTimestamp}.000Z`),cooldownPeriodMillisec=24*token.undelegationCooldown*3600*1e3,completeTimestamp=blockDate.getTime()+cooldownPeriodMillisec,undelegation={account:api.sender,symbol:token.symbol,quantity:quantity,completeTimestamp:completeTimestamp,txID:api.transactionId};await api.db.insert("pendingUndelegations",undelegation),api.emit("undelegateStart",{from:finalFrom,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalFrom}),await api.executeSmartContract("mining","handleStakeChange",{account:finalFrom,symbol:symbol,quantity:api.BigNumber(quantity).negated(),delegated:!0}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalFrom,token:token})}}}}}}};const processUndelegation=async undelegation=>{const{account:account,symbol:symbol,quantity:quantity}=undelegation,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==balance,"balance does not exist")){const originalStake=balance.stake,originalPendingUndelegations=balance.pendingUndelegations;balance.stake=calculateBalance(balance.stake,quantity,token.precision,!0),balance.pendingUndelegations=calculateBalance(balance.pendingUndelegations,quantity,token.precision,!1),api.assert(api.BigNumber(balance.pendingUndelegations).lt(originalPendingUndelegations)&&api.BigNumber(balance.stake).gt(originalStake),"cannot subtract")&&(await api.db.update("balances",balance),await api.db.remove("pendingUndelegations",undelegation),api.emit("undelegateDone",{account:account,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}))}};actions.checkPendingUndelegations=async()=>{if(api.assert("null"===api.sender,"not authorized")){const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let pendingUndelegations=await api.db.find("pendingUndelegations",{completeTimestamp:{$lte:timestamp}},1e3,0,[{index:"_id",descending:!1}]),nbPendingUndelegations=pendingUndelegations.length;for(;nbPendingUndelegations>0;){for(let index=0;indexadd?api.BigNumber(balance).plus(quantity).toFixed(precision):api.BigNumber(balance).minus(quantity).toFixed(precision),countDecimals=value=>api.BigNumber(value).dp(),findAndProcessAll=async(table,query,callback)=>{let offset=0,results=[],done=!1;for(;!done;)if(results=await api.db.find(table,query,1e3,offset),results){for(let i=0;i{if(!1===await api.db.tableExists("tokens")){await api.db.createTable("tokens",["symbol"]),await api.db.createTable("balances",["account"]),await api.db.createTable("contractsBalances",["account"]),await api.db.createTable("params"),await api.db.createTable("pendingUnstakes",["account","unstakeCompleteTimestamp"]),await api.db.createTable("delegations",["from","to"]),await api.db.createTable("pendingUndelegations",["account","completeTimestamp"]);const params={tokenCreationFee:"0",enableDelegationFee:"0",enableStakingFee:"0"};await api.db.insert("params",params)}else{const params=await api.db.findOne("params",{});if(!params.blacklist){params.blacklist=ACCOUNT_BLACKLIST,params.heAccounts=HE_ACCOUNTS;const unsets={};let useUnsets=!1;params.fixMultiTxUnstakeBalance&&(delete params.fixMultiTxUnstakeBalance,unsets.fixMultiTxUnstakeBalance="",useUnsets=!0),params.cancelBadUnstakes&&(delete params.cancelBadUnstakes,unsets.cancelBadUnstakes="",useUnsets=!0),useUnsets?await api.db.update("params",params,unsets):await api.db.update("params",params)}}};const balanceTemplate={account:null,symbol:null,balance:"0",stake:"0",pendingUnstake:"0",delegationsIn:"0",delegationsOut:"0",pendingUndelegations:"0"},addStake=async(account,token,quantity)=>{let balance=await api.db.findOne("balances",{account:account,symbol:token.symbol});null===balance&&(balance=balanceTemplate,balance.account=account,balance.symbol=token.symbol,balance=await api.db.insert("balances",balance)),void 0===balance.stake&&(balance.stake="0",balance.pendingUnstake="0");const originalStake=balance.stake;return balance.stake=calculateBalance(balance.stake,quantity,token.precision,!0),!!api.assert(api.BigNumber(balance.stake).gt(originalStake),"cannot add")&&(await api.db.update("balances",balance),void 0===token.totalStaked&&(token.totalStaked="0"),token.totalStaked=calculateBalance(token.totalStaked,quantity,token.precision,!0),await api.db.update("tokens",token),!0)},subBalance=async(account,token,quantity,table)=>{const balance=await api.db.findOne(table,{account:account,symbol:token.symbol});if(api.assert(null!==balance,"balance does not exist")&&api.assert(api.BigNumber(balance.balance).gte(quantity),"overdrawn balance")){const originalBalance=balance.balance;if(balance.balance=calculateBalance(balance.balance,quantity,token.precision,!1),api.assert(api.BigNumber(balance.balance).lt(originalBalance),"cannot subtract"))return await api.db.update(table,balance),!0}return!1},addBalance=async(account,token,quantity,table)=>{let balance=await api.db.findOne(table,{account:account,symbol:token.symbol});if(null===balance)return balance=balanceTemplate,balance.account=account,balance.symbol=token.symbol,balance.balance=quantity,await api.db.insert(table,balance),!0;const originalBalance=balance.balance;return balance.balance=calculateBalance(balance.balance,quantity,token.precision,!0),!!api.assert(api.BigNumber(balance.balance).gt(originalBalance),"cannot add")&&(await api.db.update(table,balance),!0)};actions.updateParams=async payload=>{if(api.sender!==api.owner)return;const{tokenCreationFee:tokenCreationFee,enableDelegationFee:enableDelegationFee,enableStakingFee:enableStakingFee,blacklist:blacklist,heAccounts:heAccounts}=payload,params=await api.db.findOne("params",{});tokenCreationFee&&"string"==typeof tokenCreationFee&&!api.BigNumber(tokenCreationFee).isNaN()&&api.BigNumber(tokenCreationFee).gte(0)&&(params.tokenCreationFee=tokenCreationFee),enableDelegationFee&&"string"==typeof enableDelegationFee&&!api.BigNumber(enableDelegationFee).isNaN()&&api.BigNumber(enableDelegationFee).gte(0)&&(params.enableDelegationFee=enableDelegationFee),enableStakingFee&&"string"==typeof enableStakingFee&&!api.BigNumber(enableStakingFee).isNaN()&&api.BigNumber(enableStakingFee).gte(0)&&(params.enableStakingFee=enableStakingFee),blacklist&&"object"==typeof blacklist&&(params.blacklist=blacklist),heAccounts&&"object"==typeof heAccounts&&(params.heAccounts=heAccounts),await api.db.update("params",params)},actions.updateUrl=async payload=>{const{url:url,symbol:symbol}=payload;if(api.assert(symbol&&"string"==typeof symbol&&url&&"string"==typeof url,"invalid params")&&api.assert(url.length<=255,"invalid url: max length of 255")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(token.issuer===api.sender,"must be the issuer"))try{const metadata=JSON.parse(token.metadata);api.assert(metadata&&metadata.url,"an error occured when trying to update the url")&&(metadata.url=url,token.metadata=JSON.stringify(metadata),await api.db.update("tokens",token))}catch(e){}}},actions.updateMetadata=async payload=>{const{metadata:metadata,symbol:symbol,callingContractInfo:callingContractInfo}=payload,fromVerifiedContract="hive-engine"===api.sender&&callingContractInfo&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name);if(api.assert(symbol&&"string"==typeof symbol&&metadata&&"object"==typeof metadata,"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(fromVerifiedContract||token.issuer===api.sender,"must be the issuer"))try{const finalMetadata=JSON.stringify(metadata);api.assert(finalMetadata.length<=1e3,"invalid metadata: max length of 1000")&&(token.metadata=finalMetadata,await api.db.update("tokens",token))}catch(e){}}},actions.updatePrecision=async payload=>{const{symbol:symbol,precision:precision,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol)&&api.assert(precision>0&&precision<=8&&Number.isInteger(precision),"invalid precision")){const token=await api.db.findOne("tokens",{symbol:symbol});token&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(precision>token.precision,"precision can only be increased")&&(token.precision=precision,await api.db.update("tokens",token))}},actions.transferOwnership=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to,"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});if(token&&api.assert(token.issuer===api.sender,"must be the issuer")){const finalTo=to.trim();api.assert(api.isValidAccountName(finalTo),"invalid to")&&(token.issuer=finalTo,await api.db.update("tokens",token),api.emit("transferOwnership",{from:api.sender,to:finalTo,symbol:symbol}))}}},actions.create=async payload=>{const{name:name,symbol:symbol,url:url,precision:precision,maxSupply:maxSupply,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload,params=await api.db.findOne("params",{}),{tokenCreationFee:tokenCreationFee,heAccounts:heAccounts}=params,fromVerifiedContract=callingContractInfo&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name),utilityTokenBalance=fromVerifiedContract?null:await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),authorizedCreation=!(!fromVerifiedContract&&!api.BigNumber(tokenCreationFee).lte(0)&&1!==heAccounts[api.sender])||utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(tokenCreationFee);if(api.assert(authorizedCreation,"you must have enough tokens to cover the creation fees")&&api.assert(fromVerifiedContract||!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(name&&"string"==typeof name&&symbol&&"string"==typeof symbol&&(void 0===url||url&&"string"==typeof url)&&(precision&&"number"==typeof precision||0===precision)&&maxSupply&&"string"==typeof maxSupply&&!api.BigNumber(maxSupply).isNaN(),"invalid params")&&api.assert(symbol.length>0&&symbol.length<=10&&api.validator.isAlpha(api.validator.blacklist(symbol,"."))&&api.validator.isUppercase(symbol)&&(-1===symbol.indexOf(".")||symbol.indexOf(".")>0&&symbol.indexOf(".")0&&name.length<=50,"invalid name: letters, numbers, whitespaces only, max length of 50")&&api.assert(void 0===url||url.length<=255,"invalid url: max length of 255")&&api.assert(precision>=0&&precision<=8&&Number.isInteger(precision),"invalid precision")&&api.assert(api.BigNumber(maxSupply).gt(0),"maxSupply must be positive")&&api.assert(api.BigNumber(maxSupply).lte(Number.MAX_SAFE_INTEGER),`maxSupply must be lower than ${Number.MAX_SAFE_INTEGER}`)){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null===token,"symbol already exists")){let metadata={url:void 0===url?"":url};metadata=JSON.stringify(metadata);const newToken={issuer:fromVerifiedContract?"null":api.sender,symbol:symbol,name:name,metadata:metadata,precision:precision,maxSupply:api.BigNumber(maxSupply).toFixed(precision),supply:"0",circulatingSupply:"0",stakingEnabled:!1,unstakingCooldown:1,delegationEnabled:!1,undelegationCooldown:0};await api.db.insert("tokens",newToken),api.BigNumber(tokenCreationFee).gt(0)&&void 0===heAccounts[api.sender]&&!fromVerifiedContract&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:tokenCreationFee,isSignedWithActiveKey:isSignedWithActiveKey})}}},actions.issue=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;const fromVerifiedContract="null"===api.sender&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name)||callingContractInfo&&"beedollar"===callingContractInfo.name||callingContractInfo&&"burndollar"===callingContractInfo.name;if(fromVerifiedContract||api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(fromVerifiedContract||token.issuer===api.sender,"not allowed to issue tokens")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must issue positive quantity")&&api.assert(api.BigNumber(token.maxSupply).minus(token.supply).gte(quantity),"quantity exceeds available supply")&&api.assert(api.isValidAccountName(finalTo),"invalid to")){quantity=api.BigNumber(quantity).toFixed(token.precision);let res=await addBalance(token.issuer,token,quantity,"balances");!0===res&&finalTo!==token.issuer&&await subBalance(token.issuer,token,quantity,"balances")&&(res=await addBalance(finalTo,token,quantity,"balances"),!1===res&&await addBalance(token.issuer,token,quantity,"balances")),!0===res&&(token.supply=calculateBalance(token.supply,quantity,token.precision,!0),"null"!==finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!0)),await api.db.update("tokens",token),api.emit("transferFromContract",{from:"tokens",to:finalTo,symbol:symbol,quantity:quantity}))}}},actions.issueToContract=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;const fromVerifiedContract="null"===api.sender&&-1!==VERIFIED_ISSUERS.indexOf(callingContractInfo.name);if(fromVerifiedContract||api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(fromVerifiedContract||token.issuer===api.sender,"not allowed to issue tokens")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must issue positive quantity")&&api.assert(api.BigNumber(token.maxSupply).minus(token.supply).gte(quantity),"quantity exceeds available supply")&&api.assert(finalTo.length>=3&&finalTo.length<=50,"invalid to")){quantity=api.BigNumber(quantity).toFixed(token.precision);!0===await addBalance(finalTo,token,quantity,"contractsBalances")&&(token.supply=calculateBalance(token.supply,quantity,token.precision,!0),"null"!==finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!0)),await api.db.update("tokens",token),api.emit("issueToContract",{from:"tokens",to:finalTo,symbol:symbol,quantity:quantity}))}}},actions.transfer=async payload=>{const{to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim();if(api.assert(finalTo!==api.sender,"cannot transfer to self")){const params=await api.db.findOne("params",{}),{blacklist:blacklist}=params;if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(void 0===blacklist[finalTo],`not allowed to send to ${finalTo}`)){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&await subBalance(api.sender,token,quantity,"balances")){return!1===await addBalance(finalTo,token,quantity,"balances")?(await addBalance(api.sender,token,quantity,"balances"),!1):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transfer",{from:api.sender,to:finalTo,symbol:symbol,quantity:quantity}),!0)}}}}return!1},actions.transferToContract=async payload=>{const{from:from,to:to,symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;const finalFrom=void 0===from||"null"!==api.sender?api.sender:from;if(api.assert(!0===isSignedWithActiveKey||"null"===api.sender,"you must use a custom_json signed with your active key")&&api.assert(to&&"string"==typeof to&&symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim().toLowerCase();if(api.assert(finalTo!==finalFrom,"cannot transfer to self")&&api.assert(finalTo.length>=3&&finalTo.length<=50,"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(finalFrom,token,quantity,"balances"))){!1===await addBalance(finalTo,token,quantity,"contractsBalances")?await addBalance(finalFrom,token,quantity,"balances"):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transferToContract",{from:finalFrom,to:finalTo,symbol:symbol,quantity:quantity}))}}}},actions.transferFromContract=async payload=>{if(api.assert("null"===api.sender,"not authorized")){const{from:from,to:to,symbol:symbol,type:type}=payload;let quantity=payload.quantity;const types=["user","contract"];if(api.assert(to&&"string"==typeof to&&from&&"string"==typeof from&&symbol&&"string"==typeof symbol&&type&&types.includes(type)&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim(),table="user"===type?"balances":"contractsBalances";if(api.assert("user"===type||"contract"===type&&finalTo!==from,"cannot transfer to self")){const toValid="user"===type?api.isValidAccountName(finalTo):finalTo.length>=3&&finalTo.length<=50;if(api.assert(!0===toValid,"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must transfer positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(from,token,quantity,"contractsBalances"))){!1===await addBalance(finalTo,token,quantity,table)?await addBalance(from,token,quantity,"contractsBalances"):("null"===finalTo&&(token.circulatingSupply=calculateBalance(token.circulatingSupply,quantity,token.precision,!1),await api.db.update("tokens",token)),api.emit("transferFromContract",{from:from,to:finalTo,symbol:symbol,quantity:quantity}))}}}}}};const processUnstake=async unstake=>{const{account:account,symbol:symbol,quantity:quantity,quantityLeft:quantityLeft,numberTransactionsLeft:numberTransactionsLeft}=unstake,newUnstake=unstake,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});let tokensToRelease=0,nextTokensToRelease=0;if(api.assert(null!==balance,"balance does not exist")&&(1===numberTransactionsLeft?(tokensToRelease=quantityLeft,await api.db.remove("pendingUnstakes",unstake)):(tokensToRelease=api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN),newUnstake.quantityLeft=api.BigNumber(newUnstake.quantityLeft).minus(tokensToRelease).toFixed(token.precision),newUnstake.numberTransactionsLeft-=1,nextTokensToRelease=1===newUnstake.numberTransactionsLeft?newUnstake.quantityLeft:tokensToRelease,newUnstake.nextTransactionTimestamp=api.BigNumber(newUnstake.nextTransactionTimestamp).plus(newUnstake.millisecPerPeriod).toNumber(),await api.db.update("pendingUnstakes",newUnstake)),api.BigNumber(tokensToRelease).gt(0))){const originalBalance=balance.balance,originalPendingStake=balance.pendingUnstake;balance.balance=calculateBalance(balance.balance,tokensToRelease,token.precision,!0),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,tokensToRelease,token.precision,!1),api.assert(api.BigNumber(balance.pendingUnstake).lt(originalPendingStake)&&api.BigNumber(balance.balance).gt(originalBalance),"cannot subtract")&&(api.BigNumber(nextTokensToRelease).gt(0)&&(balance.stake=calculateBalance(balance.stake,nextTokensToRelease,token.precision,!1),token.totalStaked=calculateBalance(token.totalStaked,nextTokensToRelease,token.precision,!1),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()})),await api.db.update("balances",balance),await api.db.update("tokens",token),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}),api.emit("unstake",{account:account,symbol:symbol,quantity:tokensToRelease}))}};actions.checkPendingUnstakes=async()=>{if(api.assert("null"===api.sender,"not authorized")){const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let pendingUnstakes=await api.db.find("pendingUnstakes",{nextTransactionTimestamp:{$lte:timestamp}},1e3,0,[{index:"_id",descending:!1}]),nbPendingUnstakes=pendingUnstakes.length;for(;nbPendingUnstakes>0;){for(let index=0;index{const{symbol:symbol,unstakingCooldown:unstakingCooldown,numberTransactions:numberTransactions,isSignedWithActiveKey:isSignedWithActiveKey}=payload,params=await api.db.findOne("params",{}),{enableStakingFee:enableStakingFee}=params,utilityTokenBalance=await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),enoughFunds=utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(enableStakingFee),authorized=void 0===enableStakingFee||api.BigNumber(enableStakingFee).lte(0)||enoughFunds;if(api.assert(authorized,"you must have enough tokens to cover fees")&&api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol,"invalid symbol")&&api.assert(unstakingCooldown&&Number.isInteger(unstakingCooldown)&&unstakingCooldown>0&&unstakingCooldown<=18250,"unstakingCooldown must be an integer between 1 and 18250")&&api.assert(numberTransactions&&Number.isInteger(numberTransactions)&&numberTransactions>0&&numberTransactions<=18250,"numberTransactions must be an integer between 1 and 18250")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(void 0===token.stakingEnabled||!1===token.stakingEnabled,"staking already enabled")&&(token.stakingEnabled=!0,token.totalStaked="0",token.unstakingCooldown=unstakingCooldown,token.numberTransactions=numberTransactions,await api.db.update("tokens",token),api.BigNumber(enableStakingFee).gt(0)&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:enableStakingFee,isSignedWithActiveKey:isSignedWithActiveKey}))}},actions.stake=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol}),finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(api.BigNumber(quantity).gt(0),"must stake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(api.sender,token,quantity,"balances"))){!1===await addStake(finalTo,token,quantity)?await addBalance(api.sender,token,quantity,"balances"):(api.emit("stake",{account:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo}),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}},actions.stakeFromContract=async payload=>{const{symbol:symbol,to:to,callingContractInfo:callingContractInfo}=payload;let quantity=payload.quantity;if(callingContractInfo&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol}),finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")&&api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(api.BigNumber(quantity).gt(0),"must stake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await subBalance(callingContractInfo.name,token,quantity,"contractsBalances"))){!1===await addStake(finalTo,token,quantity)?await addBalance(callingContractInfo.name,token,quantity,"balances"):(api.emit("stakeFromContract",{account:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo}),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}};const validateAvailableStake=async(balance,token,quantity)=>{let availableStakeBalance=api.BigNumber(balance.stake);return await findAndProcessAll("pendingUnstakes",{symbol:balance.symbol,account:balance.account},(async pendingUnstake=>{if(pendingUnstake.numberTransactionsLeft>1){const tokensToRelease=api.BigNumber(pendingUnstake.quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN);availableStakeBalance=availableStakeBalance.minus(pendingUnstake.quantityLeft).plus(tokensToRelease)}})),api.assert(availableStakeBalance.gte(quantity),"overdrawn stake")},startUnstake=async(account,token,quantity)=>{const balance=await api.db.findOne("balances",{account:account,symbol:token.symbol});if(!api.assert(null!==balance,"balance does not exist")||!await validateAvailableStake(balance,token,quantity))return!1;{const originalStake=balance.stake,originalPendingStake=balance.pendingUnstake,nextTokensToRelease=token.numberTransactions>1?api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN):quantity;balance.stake=calculateBalance(balance.stake,nextTokensToRelease,token.precision,!1),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,quantity,token.precision,!0),api.assert(api.BigNumber(balance.stake).lt(originalStake)&&api.BigNumber(balance.pendingUnstake).gt(originalPendingStake),"cannot subtract")&&(await api.db.update("balances",balance),token.totalStaked=calculateBalance(token.totalStaked,nextTokensToRelease,token.precision,!1),await api.db.update("tokens",token),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===token.symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:token.symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}))}const blockDate=new Date(`${api.hiveBlockTimestamp}.000Z`),cooldownPeriodMillisec=24*token.unstakingCooldown*3600*1e3,millisecPerPeriod=api.BigNumber(cooldownPeriodMillisec).dividedBy(token.numberTransactions).integerValue(api.BigNumber.ROUND_DOWN),nextTransactionTimestamp=api.BigNumber(blockDate.getTime()).plus(millisecPerPeriod).toNumber(),unstake={account:account,symbol:token.symbol,quantity:quantity,quantityLeft:quantity,nextTransactionTimestamp:nextTransactionTimestamp,numberTransactionsLeft:token.numberTransactions,millisecPerPeriod:millisecPerPeriod,txID:api.transactionId};return await api.db.insert("pendingUnstakes",unstake),!0};actions.unstake=async payload=>{const{symbol:symbol,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(api.BigNumber(quantity).gt(0),"must unstake positive quantity")&&(quantity=api.BigNumber(quantity).toFixed(token.precision),await startUnstake(api.sender,token,quantity)&&api.emit("unstakeStart",{account:api.sender,symbol:symbol,quantity:quantity}))}};const processCancelUnstake=async unstake=>{const{account:account,symbol:symbol,quantity:quantity,quantityLeft:quantityLeft,numberTransactionsLeft:numberTransactionsLeft}=unstake,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==balance,"balance does not exist")&&api.assert(api.BigNumber(balance.pendingUnstake).gte(quantityLeft),"overdrawn pendingUnstake")){const originalStake=balance.stake,originalPendingStake=balance.pendingUnstake,tokensToRelease=numberTransactionsLeft>1?api.BigNumber(quantity).dividedBy(token.numberTransactions).toFixed(token.precision,api.BigNumber.ROUND_DOWN):quantityLeft;if(balance.stake=calculateBalance(balance.stake,tokensToRelease,token.precision,!0),balance.pendingUnstake=calculateBalance(balance.pendingUnstake,quantityLeft,token.precision,!1),api.assert(api.BigNumber(balance.pendingUnstake).lt(originalPendingStake)&&api.BigNumber(balance.stake).gt(originalStake),"cannot subtract"))return await api.db.update("balances",balance),token.totalStaked=calculateBalance(token.totalStaked,tokensToRelease,token.precision,!0),await api.db.update("tokens",token),api.emit("unstakeCancel",{account:account,symbol:symbol,quantity:quantityLeft}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:tokensToRelease}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}),!0}return!1};actions.cancelUnstake=async payload=>{const{txID:txID,isSignedWithActiveKey:isSignedWithActiveKey}=payload;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(txID&&"string"==typeof txID,"invalid params")){const unstake=await api.db.findOne("pendingUnstakes",{account:api.sender,txID:txID});api.assert(unstake,"unstake does not exist")&&await processCancelUnstake(unstake)&&await api.db.remove("pendingUnstakes",unstake)}},actions.enableDelegation=async payload=>{const{symbol:symbol,undelegationCooldown:undelegationCooldown,isSignedWithActiveKey:isSignedWithActiveKey}=payload,params=await api.db.findOne("params",{}),{enableDelegationFee:enableDelegationFee}=params,utilityTokenBalance=await api.db.findOne("balances",{account:api.sender,symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'"}),enoughFunds=utilityTokenBalance&&api.BigNumber(utilityTokenBalance.balance).gte(enableDelegationFee),authorized=void 0===enableDelegationFee||api.BigNumber(enableDelegationFee).lte(0)||enoughFunds;if(api.assert(authorized,"you must have enough tokens to cover fees")&&api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol,"invalid symbol")&&api.assert(undelegationCooldown&&Number.isInteger(undelegationCooldown)&&undelegationCooldown>0&&undelegationCooldown<=18250,"undelegationCooldown must be an integer between 1 and 18250")){const token=await api.db.findOne("tokens",{symbol:symbol});api.assert(null!==token,"symbol does not exist")&&api.assert(token.issuer===api.sender,"must be the issuer")&&api.assert(!0===token.stakingEnabled,"staking not enabled")&&api.assert(void 0===token.delegationEnabled||!1===token.delegationEnabled,"delegation already enabled")&&(token.delegationEnabled=!0,token.undelegationCooldown=undelegationCooldown,await api.db.update("tokens",token),api.BigNumber(enableDelegationFee).gt(0)&&await actions.transfer({to:"null",symbol:"'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'",quantity:enableDelegationFee,isSignedWithActiveKey:isSignedWithActiveKey}))}},actions.delegate=async payload=>{const{symbol:symbol,to:to,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&to&&"string"==typeof to&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalTo=to.trim();if(api.assert(api.isValidAccountName(finalTo),"invalid to")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.delegationEnabled,"delegation not enabled")&&api.assert(finalTo!==api.sender,"cannot delegate to yourself")&&api.assert(api.BigNumber(quantity).gt(0),"must delegate positive quantity")){quantity=api.BigNumber(quantity).toFixed(token.precision);const balanceFrom=await api.db.findOne("balances",{account:api.sender,symbol:symbol});if(api.assert(null!==balanceFrom,"balanceFrom does not exist")&&await validateAvailableStake(balanceFrom,token,quantity)){void 0===balanceFrom.stake?(balanceFrom.stake="0",balanceFrom.pendingUnstake="0",balanceFrom.delegationsIn="0",balanceFrom.delegationsOut="0",balanceFrom.pendingUndelegations="0"):void 0===balanceFrom.delegationsIn&&(balanceFrom.delegationsIn="0",balanceFrom.delegationsOut="0",balanceFrom.pendingUndelegations="0",balanceFrom.delegatedStake&&(delete balanceFrom.delegatedStake,delete balanceFrom.receivedStake));let balanceTo=await api.db.findOne("balances",{account:finalTo,symbol:symbol});null===balanceTo?(balanceTo=balanceTemplate,balanceTo.account=finalTo,balanceTo.symbol=symbol,balanceTo=await api.db.insert("balances",balanceTo)):void 0===balanceTo.stake?(balanceTo.stake="0",balanceTo.pendingUnstake="0",balanceTo.delegationsIn="0",balanceTo.delegationsOut="0",balanceTo.pendingUndelegations="0"):void 0===balanceTo.delegationsIn&&(balanceTo.delegationsIn="0",balanceTo.delegationsOut="0",balanceTo.pendingUndelegations="0",balanceTo.delegatedStake&&(delete balanceTo.delegatedStake,delete balanceTo.receivedStake));let delegation=await api.db.findOne("delegations",{to:finalTo,from:api.sender,symbol:symbol});const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();null==delegation?(balanceFrom.stake=calculateBalance(balanceFrom.stake,quantity,token.precision,!1),balanceFrom.delegationsOut=calculateBalance(balanceFrom.delegationsOut,quantity,token.precision,!0),await api.db.update("balances",balanceFrom),balanceTo.delegationsIn=calculateBalance(balanceTo.delegationsIn,quantity,token.precision,!0),await api.db.update("balances",balanceTo),delegation={},delegation.from=api.sender,delegation.to=finalTo,delegation.symbol=symbol,delegation.quantity=quantity,delegation.created=timestamp,delegation.updated=timestamp,await api.db.insert("delegations",delegation),api.emit("delegate",{to:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&(await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:api.sender}),await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo})),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity,delegated:!0}),await api.executeSmartContract("mining","handleStakeChange",{account:api.sender,symbol:symbol,quantity:api.BigNumber(quantity).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:api.sender,token:token}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token})):(balanceFrom.stake=calculateBalance(balanceFrom.stake,quantity,token.precision,!1),balanceFrom.delegationsOut=calculateBalance(balanceFrom.delegationsOut,quantity,token.precision,!0),await api.db.update("balances",balanceFrom),balanceTo.delegationsIn=calculateBalance(balanceTo.delegationsIn,quantity,token.precision,!0),await api.db.update("balances",balanceTo),delegation.quantity=calculateBalance(delegation.quantity,quantity,token.precision,!0),delegation.updated=timestamp,await api.db.update("delegations",delegation),api.emit("delegate",{to:finalTo,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&(await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:api.sender}),await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalTo})),await api.executeSmartContract("mining","handleStakeChange",{account:finalTo,symbol:symbol,quantity:quantity,delegated:!0}),await api.executeSmartContract("mining","handleStakeChange",{account:api.sender,symbol:symbol,quantity:api.BigNumber(quantity).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:api.sender,token:token}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalTo,token:token}))}}}}},actions.undelegate=async payload=>{const{symbol:symbol,from:from,isSignedWithActiveKey:isSignedWithActiveKey}=payload;let quantity=payload.quantity;if(api.assert(!0===isSignedWithActiveKey,"you must use a custom_json signed with your active key")&&api.assert(symbol&&"string"==typeof symbol&&from&&"string"==typeof from&&quantity&&"string"==typeof quantity&&!api.BigNumber(quantity).isNaN(),"invalid params")){const finalFrom=from.trim();if(api.assert(finalFrom.length>=3&&finalFrom.length<=16,"invalid from")){const token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==token,"symbol does not exist")&&api.assert(countDecimals(quantity)<=token.precision,"symbol precision mismatch")&&api.assert(!0===token.delegationEnabled,"delegation not enabled")&&api.assert(finalFrom!==api.sender,"cannot undelegate from yourself")&&api.assert(api.BigNumber(quantity).gt(0),"must undelegate positive quantity")){quantity=api.BigNumber(quantity).toFixed(token.precision);const balanceTo=await api.db.findOne("balances",{account:api.sender,symbol:symbol});if(api.assert(null!==balanceTo,"balanceTo does not exist")&&api.assert(api.BigNumber(balanceTo.delegationsOut).gte(quantity),"overdrawn delegation")){const balanceFrom=await api.db.findOne("balances",{account:finalFrom,symbol:symbol});if(api.assert(null!==balanceFrom,"balanceFrom does not exist")){const delegation=await api.db.findOne("delegations",{to:finalFrom,from:api.sender,symbol:symbol});if(api.assert(null!==delegation,"delegation does not exist")&&api.assert(api.BigNumber(delegation.quantity).gte(quantity),"overdrawn delegation")){balanceTo.pendingUndelegations=calculateBalance(balanceTo.pendingUndelegations,quantity,token.precision,!0),balanceTo.delegationsOut=calculateBalance(balanceTo.delegationsOut,quantity,token.precision,!1),await api.db.update("balances",balanceTo),balanceFrom.delegationsIn=calculateBalance(balanceFrom.delegationsIn,quantity,token.precision,!1),await api.db.update("balances",balanceFrom),delegation.quantity=calculateBalance(delegation.quantity,quantity,token.precision,!1),api.BigNumber(delegation.quantity).gt(0)?await api.db.update("delegations",delegation):await api.db.remove("delegations",delegation);const blockDate=new Date(`${api.hiveBlockTimestamp}.000Z`),cooldownPeriodMillisec=24*token.undelegationCooldown*3600*1e3,completeTimestamp=blockDate.getTime()+cooldownPeriodMillisec,undelegation={account:api.sender,symbol:token.symbol,quantity:quantity,completeTimestamp:completeTimestamp,txID:api.transactionId};await api.db.insert("pendingUndelegations",undelegation),api.emit("undelegateStart",{from:finalFrom,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:finalFrom}),await api.executeSmartContract("mining","handleStakeChange",{account:finalFrom,symbol:symbol,quantity:api.BigNumber(quantity).negated(),delegated:!0}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:finalFrom,token:token})}}}}}}};const processUndelegation=async undelegation=>{const{account:account,symbol:symbol,quantity:quantity}=undelegation,balance=await api.db.findOne("balances",{account:account,symbol:symbol}),token=await api.db.findOne("tokens",{symbol:symbol});if(api.assert(null!==balance,"balance does not exist")){const originalStake=balance.stake,originalPendingUndelegations=balance.pendingUndelegations;balance.stake=calculateBalance(balance.stake,quantity,token.precision,!0),balance.pendingUndelegations=calculateBalance(balance.pendingUndelegations,quantity,token.precision,!1),api.assert(api.BigNumber(balance.pendingUndelegations).lt(originalPendingUndelegations)&&api.BigNumber(balance.stake).gt(originalStake),"cannot subtract")&&(await api.db.update("balances",balance),await api.db.remove("pendingUndelegations",undelegation),api.emit("undelegateDone",{account:account,symbol:symbol,quantity:quantity}),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:quantity}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token}))}};actions.checkPendingUndelegations=async()=>{if(api.assert("null"===api.sender,"not authorized")){const timestamp=new Date(`${api.hiveBlockTimestamp}.000Z`).getTime();let pendingUndelegations=await api.db.find("pendingUndelegations",{completeTimestamp:{$lte:timestamp}},1e3,0,[{index:"_id",descending:!1}]),nbPendingUndelegations=pendingUndelegations.length;for(;nbPendingUndelegations>0;){for(let index=0;index { + new Promise(async (resolve) => { + client = await MongoClient.connect(conf.databaseURL, { useNewUrlParser: true, useUnifiedTopology: true }); + db = await client.db(conf.databaseName); + await db.dropDatabase(); + resolve(); + }) + .then(() => { + done(); + }) + }); + + after((done) => { + new Promise(async (resolve) => { + await client.close(); + resolve(); + }) + .then(() => { + done(); + }) + }); + + beforeEach((done) => { + new Promise(async (resolve) => { + db = await client.db(conf.databaseName); + resolve(); + }) + .then(() => { + done(); + }) + }); + + afterEach((done) => { + // runs after each test in this block + new Promise(async (resolve) => { + fixture.tearDown(); + await db.dropDatabase(); + resolve(); + }) + .then(() => { + done(); + }) + }); + + it('updates parameters on the burndollar contract', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'burndollar', 'updateParams', '{ "issueDTokenFee": "1200", "updateParamsFee": "200", "burnUsageFee": "2"}')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + const res = await fixture.database.getBlockInfo(1); + const block1 = res; + const transactionsBlock1 = block1.transactions; + + // check if the params updated OK + const params = await fixture.database.findOne({ + contract: 'burndollar', + table: 'params', + query: {} + }); + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: updates parameters on the fee charges in the burndollar contract' + '\u001b[0m'); + console.log(params); + assert.equal(params.issueDTokenFee, '1200'); + assert.equal(params.updateParamsFee, '200'); + assert.equal(params.burnUsageFee, '2'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('fails to update parameters on the burndollar contract', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateParams', '{ "issueDTokenFee": "1200", "updateParamsFee": "200", "burnUsageFee": "2"}')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + const res = await fixture.database.getBlockInfo(1); + const block1 = res; + const transactionsBlock1 = block1.transactions; + + const params = await fixture.database.findOne({ + contract: 'burndollar', + table: 'params', + query: {} + }); + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: fails to update parameters on the burndollar contract' + '\u001b[0m'); + console.log(params); + + assert.equal(params.issueDTokenFee, '1000'); + assert.equal(params.updateParamsFee, '100'); + assert.equal(params.burnUsageFee, '1'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('generates errors when trying to create D tokens with wrong parameters', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "100", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "200.0", "isSignedWithActiveKey": true }')); + //user must be the owner a pre-existing token that they wish to make into corresponding D token + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTEST", "precision": 3, "maxSupply": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTEST", "quantity": "200", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + //trans #26 does user have enough BEED enough test ... trans28 signed active key test + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "URQTEST", "feePercentage": ".5", "minConvertibleAmount": "1", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "5000.0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "URQTEST", "feePercentage": ".5","minConvertibleAmount": "1", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": 156, "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "RUTTMUTTT", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + //trans 31 user must be issuer on the Parent token + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "BEE", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + // trans32 the parent token is set to burn to null by default, but a user can send it to any other account + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "URQTEST", "burnRouting": 123, "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + // trans33 fee conversion rate must be between 0 and 100% as a decimal + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "URQTEST", "feePercentage": "1.1","minConvertibleAmount": "1", "burnRouting": "drewlongshot", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + //trans 35-37 the name XXX-D token already exists + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTEST.D", "precision": 3, "maxSupply": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "URQTEST.D", "quantity": "200", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot','burndollar', 'createTokenD', '{ "symbol": "URQTEST", "feePercentage": ".5","minConvertibleAmount": "1", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + const latestBlock = await fixture.database.getLatestBlockInfo(); + const transactionsBlock1 = latestBlock.transactions;; + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: generates errors when trying to issue D tokens with wrong parameters' + '\u001b[0m') ; + console.log("26 ⚪",JSON.parse(transactionsBlock1[26].logs).errors[0]); + console.log("28 ⚪",JSON.parse(transactionsBlock1[28].logs).errors[0]); + console.log("29 ⚪",JSON.parse(transactionsBlock1[29].logs).errors[0]); + console.log("30 ⚪",JSON.parse(transactionsBlock1[30].logs).errors[0]); + console.log("31 ⚪",JSON.parse(transactionsBlock1[31].logs).errors[0]); + console.log("32 ⚪",JSON.parse(transactionsBlock1[32].logs).errors[0]); + console.log("33 ⚪",JSON.parse(transactionsBlock1[33].logs).errors[0]); + console.log("36 ⚪",JSON.parse(transactionsBlock1[36].logs).errors[0]); + + assert.equal(JSON.parse(transactionsBlock1[26].logs).errors[0], 'you must have enough BEED tokens cover the creation fees'); + assert.equal(JSON.parse(transactionsBlock1[28].logs).errors[0], 'you must use a custom_json signed with your active key'); + assert.equal(JSON.parse(transactionsBlock1[29].logs).errors[0], 'symbol must be string of length 8 or less to create a xxx.D token'); + assert.equal(JSON.parse(transactionsBlock1[30].logs).errors[0], 'symbol must be string of length 8 or less to create a xxx.D token'); + assert.equal(JSON.parse(transactionsBlock1[31].logs).errors[0], `You must be the token issuer in order to issue D token`); + assert.equal(JSON.parse(transactionsBlock1[32].logs).errors[0], `burn routing must be a valid Hive account name`); + assert.equal(JSON.parse(transactionsBlock1[33].logs).errors[0], `fee percentage must be between 0 and 1 / 0% and 100%`); + assert.equal(JSON.parse(transactionsBlock1[36].logs).errors[0], `D token must not already exist`); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('creates a D token', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 1 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "100", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + //trans24-25 user is token_issuer on a parent token has to pay 1000 Beed to issue D token, ,and not pay a Bee fee of 100 for issue of parent token creatation of parent token brings bee burned total to 2100 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTWO", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "200", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + // trans 26 BEE should still be 2100 burned and BEED should be at 1000 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "feePercentage": ".5", "minConvertibleAmount": "1", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + const latestBlock = await fixture.database.getLatestBlockInfo(); + const transactionsBlock1 = latestBlock.transactions;; + + let res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: {account:'null', symbol:'BEE'} + }); + + let token = res2; + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: creates a D token' + '\u001b[0m'); + console.log (token); + assert.equal(token.symbol, 'BEE'); + assert.equal(token.balance, '2100.00000000'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: {account:'null', symbol: 'BEED'} + }); + + token = res2; + console.log(token); + assert.equal(token.symbol, 'BEED'); + assert.equal(token.balance, '1000.0000'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'tokens', + query: {symbol: 'URQTWO'} + }); + + token = res2; + + console.log(token); + assert.equal(token.symbol, 'URQTWO'); + assert.equal(token.issuer, 'drewlongshot'); + assert.equal(token.name, 'token'); + assert.equal(token.maxSupply, '20000.000'); + assert.equal(token.supply, '200.000'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'tokens', + query: {symbol: 'URQTWO.D'} + }); + + token = res2; + + console.log(token); + assert.equal(token.symbol, 'URQTWO.D'); + assert.equal(token.issuer, 'null'); + assert.equal(token.name, 'URQTWO stablecoin'); + assert.equal(token.precision, 3); + assert.equal(token.supply, '1000.000'); + + res2 = await fixture.database.findOne({ + contract: 'burndollar', + table: 'burnpair', + query: {} + }); + + token = res2; + + console.log(token); + assert.equal(token.symbol, 'URQTWO.D'); + assert.equal(token.issuer, 'drewlongshot'); + assert.equal(token.parentSymbol,'URQTWO'); + assert.equal(token.burnRouting, 'null'); + assert.equal(token.feePercentage,'.5'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('updates the params for the D token and charges 100 BEED', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + let refBlockNumber = 74391382; + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 1 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "100", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + //trans24-25 user is token_issuer on a parent token has to pay 1000 Beed to issue D token, ,and not pay a Bee fee of 100 for issue of parent token creatation of parent token brings bee burned total to 2100 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTWO", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "200", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + // BEE should still be 2100 burned and BEED should be at 1000 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "name": "token", "feePercentage": ".5", "precision": 2, "isSignedWithActiveKey": true }')); + //trans27 update params + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "URQTWO.D", "feePercentage": ".7", "burnRouting": "whale", "isSignedWithActiveKey": true }')); + + + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + const res = await fixture.database.getBlockInfo(1); + const block1 = res; + const transactionsBlock1 = block1.transactions; + + res2 = await fixture.database.findOne({ + contract: 'burndollar', + table: 'burnpair', + query: {} + }); + + token = res2; + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: updates the params for the D token and charges 100 BEED' + '\u001b[0m'); + console.log(token); + assert.equal(token.burnRouting, 'whale'); + assert.equal(token.feePercentage, '.7'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: {account: 'drewlongshot', symbol: 'BEED'} + }); + + token = res2; + console.log(token); + assert.equal(token.balance, '880.0000'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('Fails to update the params for the D token', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 1 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "100", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + //trans24-25 user is token_issuer on a parent token has to pay 1000 Beed to issue D token, ,and not pay a Bee fee of 100 for issue of parent token creatation of parent token brings bee burned total to 2100 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTWO", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "200", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + // BEE should still be 2100 burned and BEED should be at 1000 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "name": "token", "feePercentage": ".5", "minConvertibleAmount": "1", "maxSupply": "20000", "precision": 2, "isSignedWithActiveKey": true }')); + //trans27 not signed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "URQTWO.D","name": "my new name token", "feePercentage": ".7", "burnRouting": "whale", "isSignedWithActiveKey": false }')); + //trans28 burn routing wrong + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "URQTWO.D","name": "my new name token", "feePercentage": ".7", "burnRouting": "whaldsdfe", "isSignedWithActiveKey": true }')); + //trans29+30 burn valid symbol + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": 123,"name": "my new name token", "feePercentage": ".7", "burnRouting": "whale", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "123","name": "my new name token", "feePercentage": ".7", "burnRouting": "whale", "isSignedWithActiveKey": true }')); + //trans31+32 valid perscision + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "URQTWO.D","name": "my new name token", "feePercentage": 7, "burnRouting": "whale", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'updateBurnPair', '{"symbol": "URQTWO.D","name": "my new name token", "feePercentage": "-1.1", "burnRouting": "whale", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + const latestBlock = await fixture.database.getLatestBlockInfo(); + const transactionsBlock1 = latestBlock.transactions;; + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: Fails to update the params for the D token' + '\u001b[0m'); + console.log(" ⚪ ",JSON.parse(transactionsBlock1[27].logs).errors[0]); + console.log(" ⚪ ",JSON.parse(transactionsBlock1[29].logs).errors[0]); + console.log(" ⚪ ",JSON.parse(transactionsBlock1[30].logs).errors[0]); + console.log(" ⚪ ",JSON.parse(transactionsBlock1[31].logs).errors[0]); + console.log(" ⚪ ",JSON.parse(transactionsBlock1[32].logs).errors[0]); + + assert.equal(JSON.parse(transactionsBlock1[27].logs).errors[0], 'you must use a custom_json signed with your active key'); + assert.equal(JSON.parse(transactionsBlock1[29].logs).errors[0], 'symbol must be string'); + assert.equal(JSON.parse(transactionsBlock1[30].logs).errors[0], 'D token must exist'); + assert.equal(JSON.parse(transactionsBlock1[31].logs).errors[0], 'fee percentage must be between 0 and 1 / 0% and 100%'); + assert.equal(JSON.parse(transactionsBlock1[32].logs).errors[0], 'fee percentage must be between 0 and 1 / 0% and 100%'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + + it('fails to convert token XXX to XXX.D', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 1 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "1000", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + //trans24-25 user is token_issuer on a parent token has to pay 1000 Beed to issue D token, ,and not pay a Bee fee of 100 for issue of parent token creatation of parent token brings bee burned total to 2100 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTWO", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "2000", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + // trans 26 active key + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "20", "isSignedWithActiveKey": false }')); + // trans 27-30 not enough BEED + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'transfer', '{ "symbol": "BEED", "to": "aggroed", "quantity": "1200", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "name": "token", "feePercentage": ".5", "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'aggroed', 'tokens', 'transfer', '{ "symbol": "BEED", "to": "drewlongshot", "quantity": "1200", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "name": "token", "feePercentage": ".5", "maxSupply": "20000", "isSignedWithActiveKey": true }')); + // trans 31 invalid quant + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : 20, "isSignedWithActiveKey": true }')); + // trans 32 +33 invalid symbol + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": 123, "quantity" : "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "123", "quantity" : "20", "isSignedWithActiveKey": true }')); + //34 quant > min convert + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "-2", "isSignedWithActiveKey": true }')); + //35 quant precision mismatch + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "2.0000004", "isSignedWithActiveKey": true }')); + //36-38 - not enough BEED + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'transfer', '{ "symbol": "BEED", "to": "aggroed", "quantity": "980", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'aggroed', 'tokens', 'transfer', '{ "symbol": "BEED", "to": "drewlongshot", "quantity": "980", "isSignedWithActiveKey": true }')); + //39 trying to convert more than you own + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "20100", "isSignedWithActiveKey": true }')); + //40 stable pool validation + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "150", "isSignedWithActiveKey": true }')); + //41-42 market pool validation + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "URQTWO:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "50", "isSignedWithActiveKey": true }')); + //43-47 stable pool USD value + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO:SWAP.HBD", "baseQuantity": "10", "quoteQuantity": "10", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "URQTWO:URQTWO.D", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO:URQTWO.D", "baseQuantity": "1000", "quoteQuantity": "1000", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "2000", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "50", "isSignedWithActiveKey": true }')); + //48-50 Market pool USD value + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'removeLiquidity', '{ "tokenPair": "URQTWO:URQTWO.D", "sharesOut": "99", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO:SWAP.HBD", "baseQuantity": "500", "quoteQuantity": "500", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "50", "isSignedWithActiveKey": true }')); + //51-56 drew not token issuer + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "WHALE", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'tokens', 'issue', '{ "symbol": "WHALE", "name": "token", "quantity": "2000", "to": "whale", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'tokens', 'transfer', '{ "symbol": "WHALE", "to": "drewlongshot", "quantity": "100", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "WHALE", "quantity" : "10", "feePercentage": ".5", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + const latestBlock = await fixture.database.getLatestBlockInfo(); + const transactionsBlock1 = latestBlock.transactions;; + + let res2 = await fixture.database.find({ + contract: 'marketpools', + table: 'pools', + query: { + } + }); + + let res1 = await fixture.database.find({ + contract: 'tokens', + table: 'balances', + query: { + } + }); + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: fails to convert token XXX to XXX.D' + '\u001b[0m'); + console.log(" 26 ⚪",JSON.parse(transactionsBlock1[26].logs).errors[0]); + console.log(" 28 ⚪",JSON.parse(transactionsBlock1[28].logs).errors[0]); + console.log(" 31 ⚪",JSON.parse(transactionsBlock1[31].logs).errors[0]); + console.log(" 32 ⚪",JSON.parse(transactionsBlock1[32].logs).errors[0]); + console.log(" 33 ⚪",JSON.parse(transactionsBlock1[33].logs).errors[0]); + console.log(" 34 ⚪",JSON.parse(transactionsBlock1[34].logs).errors[0]); + console.log(" 35 ⚪",JSON.parse(transactionsBlock1[35].logs).errors[0]); + console.log(" 37 ⚪",JSON.parse(transactionsBlock1[37].logs).errors[0]); + console.log(" 39 ⚪",JSON.parse(transactionsBlock1[39].logs).errors[0]); + console.log(" 40 ⚪",JSON.parse(transactionsBlock1[40].logs).errors[0]); + console.log(" 42 ⚪",JSON.parse(transactionsBlock1[42].logs).errors[0]); + console.log(" 47 ⚪",JSON.parse(transactionsBlock1[47].logs).errors[0]); + console.log(" 50 ⚪",JSON.parse(transactionsBlock1[50].logs).errors[0]); + console.log(" 56 ⚪",JSON.parse(transactionsBlock1[56].logs).errors[0]); + + + assert.equal(JSON.parse(transactionsBlock1[26].logs).errors[0], 'you must use a custom_json signed with your active key'); + assert.equal(JSON.parse(transactionsBlock1[28].logs).errors[0], 'you must have enough BEED tokens cover the creation fees'); + assert.equal(JSON.parse(transactionsBlock1[31].logs).errors[0], 'invalid params quantity'); + assert.equal(JSON.parse(transactionsBlock1[32].logs).errors[0], 'symbol must be string'); + assert.equal(JSON.parse(transactionsBlock1[33].logs).errors[0], 'parent symbol must have a child .D token'); + assert.equal(JSON.parse(transactionsBlock1[34].logs).errors[0], 'amount to convert must be >= 1'); + assert.equal(JSON.parse(transactionsBlock1[35].logs).errors[0], 'symbol precision mismatch'); + assert.equal(JSON.parse(transactionsBlock1[37].logs).errors[0], 'not enough utility tokens'); + assert.equal(JSON.parse(transactionsBlock1[39].logs).errors[0], 'not enough token balance'); + assert.equal(JSON.parse(transactionsBlock1[40].logs).errors[0], 'token must be in pool with a stable coin'); + assert.equal(JSON.parse(transactionsBlock1[42].logs).errors[0], 'token must be in pool with xxx.d token'); + assert.equal(JSON.parse(transactionsBlock1[47].logs).errors[0], 'stable token pool USD value must be at least 500'); + assert.equal(JSON.parse(transactionsBlock1[50].logs).errors[0], 'parent token and XXX.D token pool USD value must be at least 500'); + assert.equal(JSON.parse(transactionsBlock1[56].logs).errors[0], 'You must be the token issuer in order to issue D token'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('it converts XXX to XXX.D', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tknContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(bdContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(beeContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 1 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(mpContractPayload))); // update 2 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.HBD", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "drewlongshot", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "HBD Pegged", "url": "https://hive-engine.com", "symbol": "SWAP.USDT", "precision": 8, "maxSupply": "1000000000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.USDT", "to": "drewlongshot", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_PEGGED_ACCOUNT, 'tokens', 'transfer', '{ "symbol": "SWAP.HIVE", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "drewlongshot", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', '{ "symbol": "SWAP.HBD", "to": "whale", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "10000", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'whale', 'market', 'sell', '{ "symbol": "SWAP.HBD", "quantity": "10000", "price": "0.5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "quantity": "100", "price": "10", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'market', 'buy', '{ "symbol": "SWAP.HBD", "quantity": "100", "price": "0.5","isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:BEE", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:BEE", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "baseQuantity": "20000", "quoteQuantity": "200", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:BEE", "tokenSymbol": "SWAP.HIVE", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'swapTokens', '{ "tokenPair": "SWAP.HIVE:SWAP.HBD", "tokenSymbol": "SWAP.HBD", "tokenAmount": "5", "tradeType": "exactOutput", "isSignedWithActiveKey": true}')); + // now, do a convert from bee to beed + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'beedollar', 'convert', '{ "quantity": "2000.0", "isSignedWithActiveKey": true }')); + //trans26-27 user is token_issuer on a parent token has to pay 1000 Beed to issue D token, ,and not pay a Bee fee of 100 for issue of parent token creatation of parent token brings bee burned total to 2100 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "URQTWO", "precision": 3, "maxSupply": "20000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'tokens', 'issue', '{ "symbol": "URQTWO", "name": "token", "quantity": "20000", "to": "drewlongshot", "isSignedWithActiveKey": true }')); + // BEE should still be 2100 burned and BEED should be at 1000 + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'createTokenD', '{"symbol": "URQTWO", "name": "token", "burnRouting": "whale", "feePercentage": ".1", "minConvertibleAmount": "1", "maxSupply": "20000", "isSignedWithActiveKey": true }')); + //trans 29 - 34 make pools + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "URQTWO:URQTWO.D", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "URQTWO.D:SWAP.HBD", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'marketpools', 'createPool', '{ "tokenPair": "URQTWO:SWAP.USDT", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO:URQTWO.D", "baseQuantity": "500", "basePrice": "3", "quoteQuantity": "500", "maxDeviation": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO.D:SWAP.HBD", "baseQuantity": "300", "quoteQuantity": "300", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'marketpools', 'addLiquidity', '{ "tokenPair": "URQTWO:SWAP.USDT", "baseQuantity": "1000", "quoteQuantity": "1000", "isSignedWithActiveKey": true }')); + //35 convert + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'drewlongshot', 'burndollar', 'convert', '{"symbol": "URQTWO", "quantity" : "4000", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: { + account: 'whale', + symbol: 'URQTWO', + + } + }); + + let token = res2; + + console.log(" "); + console.log( '\u001b[' + 93 + 'm' + 'Test: it converts XXX to XXX.D' + '\u001b[0m'); + console.log(token); + + assert.equal(token.account, 'whale'); + assert.equal(token.symbol, 'URQTWO'); + assert.equal(token.balance, '400.000'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: { + account: 'null', + symbol: 'URQTWO', + } + }); + + token = res2; + console.log (token); + + //the reminder of the burn goes to null + assert.equal(token.account, 'null'); + assert.equal(token.symbol, 'URQTWO'); + assert.equal(token.balance, '3600'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: { + account: 'drewlongshot', + symbol: 'URQTWO.D' + + } + }); + + token = res2; + console.log (token); + + assert.equal(token.account, 'drewlongshot'); + assert.equal(token.symbol, 'URQTWO.D'); + assert.equal(token.balance, '3800.000'); + + res2 = await fixture.database.findOne({ + contract: 'tokens', + table: 'balances', + query: { + account: 'null', + symbol: 'BEED', + } + }); + + token = res2; + console.log (token); + + assert.equal(token.account, 'null'); + assert.equal(token.symbol, 'BEED'); + assert.equal(token.balance, '1001.0000'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); +}); \ No newline at end of file