From 09c69f11e38809b2b9943612cea458b41bd13fe6 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 14:51:19 -0400 Subject: [PATCH 01/12] fixed3 --- contracts/burndollar.js | 441 +++++++++++++++++ contracts/burndollar_minify.js | 71 +++ contracts/tokens.js | 33 +- contracts/tokens_minify.js | 58 ++- package.json | 1 + test/burndollar (1).js | 832 +++++++++++++++++++++++++++++++++ 6 files changed, 1419 insertions(+), 17 deletions(-) create mode 100644 contracts/burndollar.js create mode 100644 contracts/burndollar_minify.js create mode 100644 test/burndollar (1).js 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..f2df1999 --- /dev/null +++ b/contracts/burndollar_minify.js @@ -0,0 +1,71 @@ +<<<<<<< HEAD +const stablePairArray = ['SWAP.HBD', 'SWAP.USDT', 'SWAP.DAI', 'SWAP.USDC']; const countDecimals = value => api.BigNumber(value).dp(); const verifyTokenCreation = async symbolFind => !!await api.db.findOneInTable('tokens', 'tokens', { symbol: symbolFind }); const verifyTokenBalance = async (account, amount, symbolFind) => { const findTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: symbolFind }); return !(!findTokenBalance || !api.BigNumber(findTokenBalance.balance).gte(amount)); }; const checkStablePosition = (tokenPair) => { const [firstToken, secondToken] = tokenPair.split(':'); return stablePairArray.includes(firstToken) ? 'base' : !!stablePairArray.includes(secondToken) && 'quote'; }; const findMarketPools = async (parentSymbol, toggle) => { + const childSymbol = `${parentSymbol}.D`; let poolData; if (toggle === 'stable') { const stableResults = [`${parentSymbol}`, `${childSymbol}`].flatMap(pElement => stablePairArray.flatMap(sElement => [`${sElement}:${pElement}`, `${pElement}:${sElement}`])); const 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 (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 = {}; return name.includes(firstToken) && (quoteOrBasePosition = 'base'), name.includes(secondToken) && (quoteOrBasePosition = 'quote'), quoteOrBasePosition && quoteOrBasePosition === 'base' ? (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, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, + }) : quoteOrBasePosition && quoteOrBasePosition === 'quote' && (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, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, + }), returnObject; +}; const isTokenTransferVerified = (result, from, to, symbol, quantity, eventStr) => !(void 0 !== result.errors || !result.events || void 0 === 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)); 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 !1; + } 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, + }); 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, 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), updateParamsFee && typeof updateParamsFee === 'string' && !api.BigNumber(updateParamsFee).isNaN() && api.BigNumber(updateParamsFee).gte(1) && (params.updateParamsFee = updateParamsFee), burnUsageFee && typeof burnUsageFee === 'string' && !api.BigNumber(burnUsageFee).isNaN() && api.BigNumber(burnUsageFee).gte(1) && (params.burnUsageFee = burnUsageFee), minAmountConvertible && typeof minAmountConvertible === 'string' && !minAmountConvertible.isNaN() && minAmountConvertible.gte(1) && (params.minAmountConvertible = minAmountConvertible), dTokenToIssuer && typeof dTokenToIssuer === 'string' && !api.BigNumber(dTokenToIssuer).isNaN() && api.BigNumber(dTokenToIssuer).gte(1) && (params.dTokenToIssuer = dTokenToIssuer), burnToken && typeof burnToken === 'string') { await api.db.findOneInTable('tokens', 'tokens', { symbol: burnToken }) && (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(!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 && 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 = 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 && 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 !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, + }), 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 = 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 && 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, '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', {}); 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); 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, + }), api.emit('updated params', { symbol, burnRouting, feePercentage })); + } + } +}, actions.convert = async (payload) => { + const { symbol, quantity, isSignedWithActiveKey } = payload; if (api.assert(!0 === isSignedWithActiveKey, '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); const 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 && 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); const 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'; let 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, feeRouting: parentPairParams.burnRouting, parentSymbol: keyname, precision: parentPairParams.precision, childIssued: xxxdToIssue, parentPriceInUSD: calcResultParentPool.parentPrice, + }); + } + } + } + } +}; +======= +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})}}}}}; +>>>>>>> refs/remotes/origin/qa diff --git a/contracts/tokens.js b/contracts/tokens.js index afb3ced6..ee448c00 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 @@ -336,7 +337,7 @@ actions.transferOwnership = async (payload) => { token.issuer = finalTo; await api.db.update('tokens', token); api.emit('transferOwnership', { - from: api.sender, to: finalTo, symbol + from: api.sender, to: finalTo, symbol, }); } } @@ -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') @@ -440,11 +440,12 @@ actions.issue = async (payload) => { to, symbol, isSignedWithActiveKey, callingContractInfo, } = payload; - let quantity = payload.quantity; + let { quantity } = 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') @@ -502,7 +503,7 @@ actions.issueToContract = async (payload) => { to, symbol, isSignedWithActiveKey, callingContractInfo, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; const fromVerifiedContract = (api.sender === 'null' && VERIFIED_ISSUERS.indexOf(callingContractInfo.name) !== -1); @@ -552,7 +553,7 @@ actions.transfer = async (payload) => { const { to, symbol, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + const { quantity } = payload; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(to && typeof to === 'string' @@ -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'); @@ -608,7 +609,7 @@ actions.transferToContract = async (payload) => { const { from, to, symbol, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; const finalFrom = (from === undefined || api.sender !== 'null') ? api.sender : from; @@ -658,7 +659,7 @@ actions.transferFromContract = async (payload) => { const { from, to, symbol, type, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; const types = ['user', 'contract']; if (api.assert(to && typeof to === 'string' @@ -886,7 +887,7 @@ actions.stake = async (payload) => { to, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -934,7 +935,7 @@ actions.stakeFromContract = async (payload) => { to, callingContractInfo, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; // can only be called from a contract if (callingContractInfo @@ -1059,7 +1060,7 @@ const startUnstake = async (account, token, quantity) => { actions.unstake = async (payload) => { const { symbol, isSignedWithActiveKey } = payload; - let quantity = payload.quantity; + let { quantity } = payload; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -1200,7 +1201,7 @@ actions.delegate = async (payload) => { to, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -1378,7 +1379,7 @@ actions.undelegate = async (payload) => { from, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + let { quantity } = payload; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' diff --git a/contracts/tokens_minify.js b/contracts/tokens_minify.js index 59c6dd57..4f5ac5d6 100644 --- a/contracts/tokens_minify.js +++ b/contracts/tokens_minify.js @@ -1 +1,57 @@ -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),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token})),await api.db.update("balances",balance),await api.db.update("tokens",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 47 && code < 58)) return !1; } return !0; } return !1; } function validateDateRange(startDate, endDate, maxDays) { + if (!api.assert(validateDateTime(startDate) && validateDateTime(endDate), 'invalid datetime format: YYYY-MM-DDThh:mm:ss.sssZ')) return !1; const now = new Date(`${api.hiveBlockTimestamp}.000Z`); const start = new Date(startDate); const end = new Date(endDate); if (!api.assert(api.BigNumber(start.getTime()).lt(api.BigNumber(end.getTime()).minus(864e5)), 'dates must be at least 1 day apart') || !api.assert(api.BigNumber(start.getTime()).gt(api.BigNumber(now.getTime()).plus(864e5)), 'startDate must be at least 1 day in the future')) return !1; const rangeDays = api.BigNumber(start.getTime()).minus(end.getTime()).abs().dividedBy(864e5) + .toFixed(0, api.BigNumber.ROUND_CEIL); return !!api.assert(api.BigNumber(rangeDays).lte(maxDays), 'date range exceeds DTF maxDays'); +} function validateDateChange(proposal, newDate, maxDays) { + if (!api.assert(validateDateTime(newDate), 'invalid datetime format: YYYY-MM-DDThh:mm:ss.sssZ')) return !1; const start = new Date(proposal.startDate); const cur = new Date(proposal.endDate); const repl = new Date(newDate); if (!api.assert(api.BigNumber(start.getTime()).lt(api.BigNumber(repl.getTime()).minus(864e5)), 'dates must be at least 1 day apart')) return !1; if (!api.assert(repl <= cur, 'date can only be reduced')) return !1; const rangeDays = api.BigNumber(start.getTime()).minus(repl.getTime()).abs().dividedBy(864e5) + .toFixed(0, api.BigNumber.ROUND_CEIL); return !!api.assert(api.BigNumber(rangeDays).lte(maxDays), 'date range exceeds DTF maxDays'); +} function validatePending(proposal) { const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); return new Date(proposal.endDate) >= blockDate; } async function updateProposalWeight(id, deltaApprovalWeight, deltaToken = null) { const proposal = await api.db.findOne('proposals', { _id: id }); if (proposal && validatePending(proposal)) { if (deltaToken) { if ((await api.db.findOne('funds', { id: proposal.fundId })).voteToken !== deltaToken.symbol) return !0; } return proposal.approvalWeight = { $numberDecimal: api.BigNumber(proposal.approvalWeight.$numberDecimal).plus(deltaApprovalWeight) }, await api.db.update('proposals', proposal), !0; } return !1; } async function checkPendingProposals(dtf, params) { + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.payToken }); const tickPayRatio = api.BigNumber(params.dtfTickHours).dividedBy(24); const funded = []; const fundedLog = []; let proposals; let offset = 0; let runningPay = api.BigNumber(dtf.maxAmountPerDay).times(tickPayRatio); for (;runningPay.gt(0);) { + proposals = await api.db.find('proposals', { + fundId: dtf.id, active: !0, approvalWeight: { $gt: { $numberDecimal: api.BigNumber(dtf.voteThreshold) } }, startDate: { $lte: blockDate.toISOString() }, endDate: { $gte: blockDate.toISOString() }, + }, params.processQueryLimit, offset, [{ index: 'byApprovalWeight', descending: !0 }, { index: '_id', descending: !1 }]); for (let i = 0; i < proposals.length; i += 1) { if (api.BigNumber(proposals[i].amountPerDay).times(tickPayRatio).gte(runningPay)) { proposals[i].tickPay = runningPay.toFixed(payTokenObj.precision, api.BigNumber.ROUND_DOWN), funded.push(proposals[i]), runningPay = api.BigNumber(0); break; }proposals[i].tickPay = api.BigNumber(proposals[i].amountPerDay).times(tickPayRatio).toFixed(payTokenObj.precision, api.BigNumber.ROUND_DOWN), funded.push(proposals[i]), runningPay = runningPay.minus(proposals[i].tickPay); } if (proposals.length < params.processQueryLimit) break; offset += params.processQueryLimit; + } for (let i = 0; i < funded.length; i += 1) { const fund = funded[i]; fundedLog.push({ id: fund._id, tickPay: fund.tickPay }), fund.payout.type === 'user' ? await api.executeSmartContract('tokens', 'issue', { to: fund.payout.name, symbol: payTokenObj.symbol, quantity: fund.tickPay }) : fund.payout.type === 'contract' && (await api.executeSmartContract('tokens', 'issueToContract', { to: fund.payout.name, symbol: payTokenObj.symbol, quantity: fund.tickPay }), await api.executeSmartContract(fund.payout.name, 'receiveDtfTokens', { data: fund.payout.contractPayload, symbol: payTokenObj.symbol, quantity: fund.tickPay })); }dtf.lastTickTime = api.BigNumber(blockDate.getTime()).toNumber(), await api.db.update('funds', dtf), api.emit('fundProposals', { fundId: dtf.id, funded: fundedLog }); +}actions.createSSC = async () => { + if (!1 === await api.db.tableExists('funds')) { + await api.db.createTable('funds', ['id', 'lastTickTime']), await api.db.createTable('proposals', ['fundId', { name: 'byApprovalWeight', index: { fundId: 1, approvalWeight: 1 } }]), await api.db.createTable('approvals', ['from', 'to']), await api.db.createTable('accounts', [], { primaryKey: ['account'] }), await api.db.createTable('params'); const params = { + dtfCreationFee: '1000', dtfUpdateFee: '300', dtfTickHours: '24', maxDtfsPerBlock: 40, maxAccountApprovals: 50, processQueryLimit: 1e3, + }; await api.db.insert('params', params); + } else { const params = await api.db.findOne('params', {}); if (!params.updateIndex) { const dtfs = await api.db.find('funds', {}); const voteTokens = new Set(); for (let i = 0; i < dtfs.length; i += 1)voteTokens.add(dtfs[i].voteToken); const resetAccounts = await api.db.find('accounts', {}); for (let i = 0; i < resetAccounts.length; i += 1) { const acct = resetAccounts[i]; acct.weights = acct.weights.filter(ele => voteTokens.has(ele.symbol)), await api.db.update('accounts', acct); } const resetProposals = await api.db.find('proposals', {}); for (let i = 0; i < resetProposals.length; i += 1) { const prop = resetProposals[i]; const propFund = dtfs.find(x => x.id === prop.fundId); const propApprovals = await api.db.find('approvals', { to: prop._id }); let newApprovalWeight = api.BigNumber('0'); for (let j = 0; j < propApprovals.length; j += 1) { const approval = propApprovals[j]; const approvalAcctWgt = resetAccounts.find(x => x.account === approval.from).weights.find(x => x.symbol === propFund.voteToken); newApprovalWeight = newApprovalWeight.plus(approvalAcctWgt.weight); }prop.approvalWeight = { $numberDecimal: newApprovalWeight }, await api.db.update('proposals', prop); }params.updateIndex = 1, await api.db.update('params', params); } } +}, actions.updateParams = async (payload) => { + const { + dtfCreationFee, dtfUpdateFee, dtfTickHours, maxDtfsPerBlock, maxAccountApprovals, processQueryLimit, + } = payload; if (api.sender !== api.owner) return; const params = await api.db.findOne('params', {}); if (dtfCreationFee) { if (!api.assert(typeof dtfCreationFee === 'string' && !api.BigNumber(dtfCreationFee).isNaN() && api.BigNumber(dtfCreationFee).gte(0), 'invalid dtfCreationFee')) return; params.dtfCreationFee = dtfCreationFee; } if (dtfUpdateFee) { if (!api.assert(typeof dtfUpdateFee === 'string' && !api.BigNumber(dtfUpdateFee).isNaN() && api.BigNumber(dtfUpdateFee).gte(0), 'invalid dtfUpdateFee')) return; params.dtfUpdateFee = dtfUpdateFee; } if (dtfTickHours) { if (!api.assert(typeof dtfTickHours === 'string' && api.BigNumber(dtfTickHours).isInteger() && api.BigNumber(dtfTickHours).gte(1), 'invalid dtfTickHours')) return; params.dtfTickHours = dtfTickHours; } if (maxDtfsPerBlock) { if (!api.assert(typeof maxDtfsPerBlock === 'string' && api.BigNumber(maxDtfsPerBlock).isInteger() && api.BigNumber(maxDtfsPerBlock).gte(1), 'invalid maxDtfsPerBlock')) return; params.maxDtfsPerBlock = api.BigNumber(maxDtfsPerBlock).toNumber(); } if (maxAccountApprovals) { if (!api.assert(typeof maxAccountApprovals === 'string' && api.BigNumber(maxAccountApprovals).isInteger() && api.BigNumber(maxAccountApprovals).gte(1), 'invalid maxDtfsPerBlock')) return; params.maxAccountApprovals = api.BigNumber(maxAccountApprovals).toNumber(); } if (processQueryLimit) { if (!api.assert(typeof processQueryLimit === 'string' && api.BigNumber(processQueryLimit).isInteger() && api.BigNumber(processQueryLimit).gte(1), 'invalid processQueryLimit')) return; params.processQueryLimit = api.BigNumber(processQueryLimit).toNumber(); } await api.db.update('params', params); +}, actions.createFund = async (payload) => { + const { + payToken, voteToken, voteThreshold, maxDays, maxAmountPerDay, proposalFee, isSignedWithActiveKey, + } = payload; const params = await api.db.findOne('params', {}); const { dtfCreationFee } = params; const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); const authorizedCreation = !(!api.BigNumber(dtfCreationFee).lte(0) && api.sender !== api.owner) || utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(dtfCreationFee); if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(typeof voteThreshold === 'string' && api.BigNumber(voteThreshold).gt(0), 'invalid voteThreshold: greater than 0') && api.assert(typeof maxDays === 'string' && api.BigNumber(maxDays).isInteger() && api.BigNumber(maxDays).gt(0) && api.BigNumber(maxDays).lte(730), 'invalid maxDays: integer between 1 and 730') && api.assert(typeof maxAmountPerDay === 'string' && api.BigNumber(maxAmountPerDay).gt(0), 'invalid maxAmountPerDay: greater than 0')) { + if (proposalFee) { if (!api.assert(typeof proposalFee === 'object' && typeof proposalFee.method === 'string' && FeeMethod.indexOf(proposalFee.method) !== -1 && typeof proposalFee.symbol === 'string' && typeof proposalFee.amount === 'string' && api.BigNumber(proposalFee.amount).gt(0), 'invalid proposalFee')) return; const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: proposalFee.symbol }); if (!api.assert(feeTokenObj && api.BigNumber(proposalFee.amount).dp() <= feeTokenObj.precision, 'invalid proposalFee token or precision')) return; } const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: payToken }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: voteToken }); if (!validateTokens(payTokenObj, voteTokenObj) || !api.assert(api.BigNumber(maxAmountPerDay).dp() <= payTokenObj.precision, 'maxAmountPerDay precision mismatch') || !api.assert(api.BigNumber(voteThreshold).dp() <= voteTokenObj.precision, 'voteThreshold precision mismatch')) return; const now = new Date(`${api.hiveBlockTimestamp}.000Z`); const newDtf = { + payToken, voteToken, voteThreshold, maxDays, maxAmountPerDay, proposalFee, active: !1, creator: api.sender, lastTickTime: now.getTime(), + }; newDtf.id = `${payToken}:${voteToken}`; const existingDtf = await api.db.findOne('funds', { id: newDtf.id }); if (!api.assert(!existingDtf, 'DTF already exists')) return; const insertedDtf = await api.db.insert('funds', newDtf); api.sender !== api.owner && api.sender !== 'null' && api.BigNumber(dtfCreationFee).gt(0) && await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: dtfCreationFee, isSignedWithActiveKey, + }), api.emit('createFund', { id: insertedDtf.id }); + } +}, actions.updateFund = async (payload) => { + const { + fundId, voteThreshold, maxDays, maxAmountPerDay, proposalFee, isSignedWithActiveKey, + } = payload; const params = await api.db.findOne('params', {}); const { dtfUpdateFee } = params; const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); const authorizedUpdate = !(!api.BigNumber(dtfUpdateFee).lte(0) && api.sender !== api.owner) || utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(dtfUpdateFee); if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(typeof voteThreshold === 'string' && api.BigNumber(voteThreshold).gt(0), 'invalid voteThreshold: greater than 0') && api.assert(typeof maxDays === 'string' && api.BigNumber(maxDays).isInteger() && api.BigNumber(maxDays).gt(0) && api.BigNumber(maxDays).lte(730), 'invalid maxDays: integer between 1 and 730') && api.assert(typeof maxAmountPerDay === 'string' && api.BigNumber(maxAmountPerDay).gt(0), 'invalid maxAmountPerDay: greater than 0')) { + if (proposalFee) { if (!api.assert(typeof proposalFee === 'object' && typeof proposalFee.method === 'string' && FeeMethod.indexOf(proposalFee.method) !== -1 && typeof proposalFee.symbol === 'string' && typeof proposalFee.amount === 'string' && api.BigNumber(proposalFee.amount).gt(0), 'invalid proposalFee')) return; const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: proposalFee.symbol }); if (!api.assert(feeTokenObj && api.BigNumber(proposalFee.amount).dp() <= feeTokenObj.precision, 'invalid proposalFee token or precision')) return; } const existingDtf = await api.db.findOne('funds', { id: fundId }); if (!api.assert(existingDtf, 'DTF not found') || !api.assert(existingDtf.creator === api.sender || api.owner === api.sender, 'must be DTF creator')) return; const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingDtf.payToken }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingDtf.voteToken }); if (!api.assert(api.BigNumber(maxAmountPerDay).dp() <= payTokenObj.precision, 'maxAmountPerDay precision mismatch') || !api.assert(api.BigNumber(voteThreshold).dp() <= voteTokenObj.precision, 'voteThreshold precision mismatch')) return; existingDtf.voteThreshold = voteThreshold, existingDtf.maxDays = maxDays, existingDtf.maxAmountPerDay = maxAmountPerDay, proposalFee && (existingDtf.proposalFee = proposalFee), await api.db.update('funds', existingDtf), api.sender !== api.owner && api.sender !== 'null' && api.BigNumber(dtfUpdateFee).gt(0) && await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: dtfUpdateFee, isSignedWithActiveKey, + }), api.emit('updateFund', { id: fundId }); + } +}, actions.setDtfActive = async (payload) => { const { fundId, active, isSignedWithActiveKey } = payload; if (!api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key')) return; const dtf = await api.db.findOne('funds', { id: fundId }); api.assert(dtf, 'DTF does not exist') && api.assert(dtf.creator === api.sender || api.owner === api.sender, 'must be DTF creator') && (dtf.active = !!active, await api.db.update('funds', dtf), api.emit('setDtfActive', { id: dtf.id, active: dtf.active })); }, actions.createProposal = async (payload) => { + const { + fundId, title, startDate, endDate, amountPerDay, authorPermlink, payout, isSignedWithActiveKey, + } = payload; const dtf = await api.db.findOne('funds', { id: fundId }); if (!api.assert(dtf, 'DTF does not exist')) return; let authorizedCreation = !0; if (dtf.proposalFee) { const feeTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.proposalFee.symbol }); authorizedCreation = !(!api.BigNumber(dtf.proposalFee.amount).lte(0) && api.sender !== api.owner) || feeTokenBalance && api.BigNumber(feeTokenBalance.balance).gte(dtf.proposalFee.amount); } if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(!0 === dtf.active, 'DTF is not active') && api.assert(typeof title === 'string' && title.length > 0 && title.length <= 80, 'invalid title: between 1 and 80 characters') && api.assert(typeof authorPermlink === 'string' && authorPermlink.length > 0 && authorPermlink.length <= 255, 'invalid authorPermlink: between 1 and 255 characters') && api.assert(typeof amountPerDay === 'string' && api.BigNumber(amountPerDay).isInteger() && api.BigNumber(amountPerDay).gt(0), 'invalid amountPerDay: greater than 0') && api.assert(api.BigNumber(amountPerDay).lte(dtf.maxAmountPerDay), 'invalid amountPerDay: exceeds DTF maxAmountPerDay') && api.assert(typeof payout === 'object' && typeof payout.type === 'string' && PayoutType.indexOf(payout.type) !== -1 && (payout.type !== 'contract' || typeof payout.contractPayload === 'object') && typeof payout.name === 'string' && payout.name.length >= 3 && payout.name.length <= 50, 'invalid payout settings') && validateDateRange(startDate, endDate, dtf.maxDays)) { + const newProposal = { + fundId, title, startDate, endDate, amountPerDay, authorPermlink, payout, creator: api.sender, approvalWeight: { $numberDecimal: '0' }, active: !0, + }; const insertedProposal = await api.db.insert('proposals', newProposal); if (api.sender !== api.owner && dtf.proposalFee) if (dtf.proposalFee.method === 'burn') await api.executeSmartContract('tokens', 'transfer', { to: 'null', symbol: dtf.proposalFee.symbol, quantity: dtf.proposalFee.amount }); else if (dtf.proposalFee.method === 'issuer') { const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.proposalFee.symbol }); await api.executeSmartContract('tokens', 'transfer', { to: feeTokenObj.issuer, symbol: dtf.proposalFee.symbol, quantity: dtf.proposalFee.amount }); }api.emit('createProposal', { id: insertedProposal._id }); + } +}, actions.updateProposal = async (payload) => { + const { + id, title, endDate, amountPerDay, authorPermlink, isSignedWithActiveKey, + } = payload; if (!api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) return; const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (!api.assert(proposal, 'proposal does not exist') || !api.assert(proposal.creator === api.sender || api.owner === api.sender, 'must be proposal creator')) return; const dtf = await api.db.findOne('funds', { id: proposal.fundId, active: !0 }); api.assert(dtf, 'DTF does not exist or inactive') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(!0 === dtf.active, 'DTF is not active') && api.assert(!0 === proposal.active, 'proposal is not active') && api.assert(typeof title === 'string' && title.length > 0 && title.length <= 80, 'invalid title: between 1 and 80 characters') && api.assert(typeof authorPermlink === 'string' && authorPermlink.length > 0 && authorPermlink.length <= 255, 'invalid authorPermlink: between 1 and 255 characters') && api.assert(typeof amountPerDay === 'string' && api.BigNumber(amountPerDay).isInteger() && api.BigNumber(amountPerDay).gt(0) && api.BigNumber(amountPerDay).lte(proposal.amountPerDay), 'invalid amountPerDay: greater than 0 and cannot be increased') && api.assert(api.BigNumber(amountPerDay).lte(dtf.maxAmountPerDay), 'invalid amountPerDay: exceeds DTF maxAmountPerDay') && validateDateChange(proposal, endDate, dtf.maxDays) && (proposal.title = title, proposal.endDate = endDate, proposal.amountPerDay = amountPerDay, proposal.authorPermlink = authorPermlink, await api.db.update('proposals', proposal), api.emit('updateProposal', { id: proposal._id })); +}, actions.disableProposal = async (payload) => { const { id, isSignedWithActiveKey } = payload; if (!api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) return; const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); api.assert(proposal, 'proposal does not exist') && api.assert(!0 === proposal.active, 'proposal already disabled') && api.assert(proposal.creator === api.sender || api.owner === api.sender, 'must be proposal creator') && (proposal.active = !1, await api.db.update('proposals', proposal), api.emit('disableProposal', { id: proposal._id })); }, actions.approveProposal = async (payload) => { const { id } = payload; const params = await api.db.findOne('params', {}); if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (api.assert(proposal, 'proposal does not exist') && api.assert(validatePending(proposal), 'proposal is not pending')) { const dtf = await api.db.findOne('funds', { id: proposal.fundId }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.voteToken }); let acct = await api.db.findOne('accounts', { account: api.sender }); acct === null && (acct = { account: api.sender, weights: [] }, acct = await api.db.insert('accounts', acct)); let activeApprovals = 0; const approvals = await api.db.find('approvals', { from: api.sender, proposalPending: !0 }, params.maxAccountApprovals, 0, [{ index: '_id', descending: !0 }]); for (let index = 0; index < approvals.length; index += 1) { const approval = approvals[index]; const approvalProposal = await api.db.findOne('proposals', { _id: approval.to }); approvalProposal && validatePending(approvalProposal) ? activeApprovals += 1 : (approval.proposalPending = !1, await api.db.update('approvals', approval)); } if (!api.assert(activeApprovals < params.maxAccountApprovals, `you can only approve ${params.maxAccountApprovals} active proposals`)) return; let approval = await api.db.findOne('approvals', { from: api.sender, to: proposal._id }); if (api.assert(approval === null, 'you already approved this proposal')) { approval = { from: api.sender, to: proposal._id, proposalPending: !0 }, await api.db.insert('approvals', approval); const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.voteToken }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)); const wIndex = acct.weights.findIndex(x => x.symbol === dtf.voteToken); wIndex !== -1 ? acct.weights[wIndex].weight = approvalWeight : acct.weights.push({ symbol: dtf.voteToken, weight: approvalWeight }), await api.db.update('accounts', acct), await updateProposalWeight(proposal._id, approvalWeight), api.emit('approveProposal', { id: proposal._id }); } } } }, actions.disapproveProposal = async (payload) => { const { id } = payload; if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (api.assert(proposal, 'proposal does not exist') && api.assert(validatePending(proposal), 'proposal is not pending')) { const dtf = await api.db.findOne('funds', { id: proposal.fundId }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.voteToken }); let acct = await api.db.findOne('accounts', { account: api.sender }); acct === null && (acct = { account: api.sender, weights: [] }, acct = await api.db.insert('accounts', acct)); const approval = await api.db.findOne('approvals', { from: api.sender, to: proposal._id }); if (api.assert(approval !== null, 'you have not approved this proposal')) { await api.db.remove('approvals', approval); const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.voteToken }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)); const wIndex = acct.weights.findIndex(x => x.symbol === dtf.voteToken); wIndex !== -1 ? acct.weights[wIndex].weight = approvalWeight : acct.weights.push({ symbol: dtf.voteToken, weight: approvalWeight }), await api.db.update('accounts', acct), await updateProposalWeight(proposal._id, api.BigNumber(approvalWeight).negated()), api.emit('disapproveProposal', { id: proposal._id }); } } } }, actions.updateProposalApprovals = async (payload) => { const { account, token, callingContractInfo } = payload; if (void 0 === callingContractInfo) return; if (callingContractInfo.name !== 'tokens') return; const acct = await api.db.findOne('accounts', { account }); if (acct !== null) { const params = await api.db.findOne('params', {}); const wIndex = acct.weights.findIndex(x => x.symbol === token.symbol); if (wIndex !== -1) { const balance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: token.symbol }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(token.precision, api.BigNumber.ROUND_HALF_UP)); let oldApprovalWeight = 0; oldApprovalWeight = acct.weights[wIndex].weight, acct.weights[wIndex].weight = approvalWeight; const deltaApprovalWeight = api.BigNumber(approvalWeight).minus(oldApprovalWeight).dp(token.precision, api.BigNumber.ROUND_HALF_UP); if (!api.BigNumber(deltaApprovalWeight).eq(0)) { await api.db.update('accounts', acct); const approvals = await api.db.find('approvals', { from: account, proposalPending: !0 }, params.maxAccountApprovals, 0, [{ index: '_id', descending: !0 }]); for (let index = 0; index < approvals.length; index += 1) { const approval = approvals[index]; await updateProposalWeight(approval.to, deltaApprovalWeight, token) || (approval.proposalPending = !1, await api.db.update('approvals', approval)); } } } } }, actions.checkPendingDtfs = async () => { if (api.assert(api.sender === 'null', 'not authorized')) { const params = await api.db.findOne('params', {}); const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const tickTime = api.BigNumber(blockDate.getTime()).minus(3600 * params.dtfTickHours * 1e3).toNumber(); const pendingDtfs = await api.db.find('funds', { active: !0, lastTickTime: { $lte: tickTime } }, params.maxDtfsPerBlock, 0, [{ index: 'lastTickTime', descending: !1 }, { index: '_id', descending: !1 }]); for (let i = 0; i < pendingDtfs.length; i += 1) await checkPendingProposals(pendingDtfs[i], params); } }; +======= +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","burndollar"],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=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),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token})),await api.db.update("balances",balance),await api.db.update("tokens",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>>>>>> refs/remotes/origin/qa diff --git a/package.json b/package.json index 2d6cbcf0..a34784c8 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "test19": "./node_modules/mocha/bin/_mocha ./test/hivepegged.js --exit", "test20": "./node_modules/mocha/bin/_mocha ./test/nftairdrops.js --exit", "test21": "./node_modules/mocha/bin/_mocha ./test/beedollar.js --exit", + "test22": "./node_modules/mocha/bin/_mocha ./test/beedollar.js --exit", "test23": "./node_modules/mocha/bin/_mocha ./test/resourcemanager.js --exit" }, "engines": { diff --git a/test/burndollar (1).js b/test/burndollar (1).js new file mode 100644 index 00000000..c7efa4e4 --- /dev/null +++ b/test/burndollar (1).js @@ -0,0 +1,832 @@ +/* eslint-disable */ +const assert = require('assert').strict; +const { MongoClient } = require('mongodb'); +const { CONSTANTS } = require('../libs/Constants'); +const { Database } = require('../libs/Database'); +const blockchain = require('../plugins/Blockchain'); +const { Transaction } = require('../libs/Transaction'); +const { setupContractPayload } = require('../libs/util/contractUtil'); +const { Fixture, conf } = require('../libs/util/testing/Fixture'); +const { TableAsserts } = require('../libs/util/testing/TableAsserts'); +const { assertError } = require('../libs/util/testing/Asserts'); + +const tknContractPayload = setupContractPayload('tokens', './contracts/tokens_minify.js'); +const bdContractPayload = setupContractPayload('burndollar', './contracts/burndollar_minify.js'); +const beeContractPayload = setupContractPayload('beedollar', './contracts/beedollar_minify.js'); +const mpContractPayload = setupContractPayload('marketpools', './contracts/marketpools_minify.js'); + +const fixture = new Fixture(); +const tableAsserts = new TableAsserts(fixture); + +// test cases for burndollar smart contract +describe('burndollar', function () { + this.timeout(25000); + + before((done) => { + 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 From 18c2c0f88df29130a313c860720c848636e3641c Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 14:53:47 -0400 Subject: [PATCH 02/12] fixed4 --- test/{burndollar (1).js => burndollar.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{burndollar (1).js => burndollar.js} (100%) diff --git a/test/burndollar (1).js b/test/burndollar.js similarity index 100% rename from test/burndollar (1).js rename to test/burndollar.js From 0d5456533e0980c402e9ef91e4cc9854cf8e9076 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:04:54 -0400 Subject: [PATCH 03/12] spelling --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a34784c8..485ce3c3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "test19": "./node_modules/mocha/bin/_mocha ./test/hivepegged.js --exit", "test20": "./node_modules/mocha/bin/_mocha ./test/nftairdrops.js --exit", "test21": "./node_modules/mocha/bin/_mocha ./test/beedollar.js --exit", - "test22": "./node_modules/mocha/bin/_mocha ./test/beedollar.js --exit", + "test22": "./node_modules/mocha/bin/_mocha ./test/burndollar.js --exit", "test23": "./node_modules/mocha/bin/_mocha ./test/resourcemanager.js --exit" }, "engines": { From d40b3965c63c86553eb77699c1796972ab11db5d Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:23:56 -0400 Subject: [PATCH 04/12] save on format error --- contracts/tokens.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index ee448c00..cf0aa050 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -440,7 +440,7 @@ actions.issue = async (payload) => { to, symbol, isSignedWithActiveKey, callingContractInfo, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; const fromVerifiedContract = (api.sender === 'null' && VERIFIED_ISSUERS.indexOf(callingContractInfo.name) !== -1) @@ -503,7 +503,7 @@ actions.issueToContract = async (payload) => { to, symbol, isSignedWithActiveKey, callingContractInfo, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; const fromVerifiedContract = (api.sender === 'null' && VERIFIED_ISSUERS.indexOf(callingContractInfo.name) !== -1); @@ -609,7 +609,7 @@ actions.transferToContract = async (payload) => { const { from, to, symbol, isSignedWithActiveKey, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; const finalFrom = (from === undefined || api.sender !== 'null') ? api.sender : from; @@ -659,7 +659,7 @@ actions.transferFromContract = async (payload) => { const { from, to, symbol, type, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; const types = ['user', 'contract']; if (api.assert(to && typeof to === 'string' @@ -887,7 +887,7 @@ actions.stake = async (payload) => { to, isSignedWithActiveKey, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -935,7 +935,7 @@ actions.stakeFromContract = async (payload) => { to, callingContractInfo, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; // can only be called from a contract if (callingContractInfo @@ -1060,7 +1060,7 @@ const startUnstake = async (account, token, quantity) => { actions.unstake = async (payload) => { const { symbol, isSignedWithActiveKey } = payload; - let { quantity } = payload; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -1201,7 +1201,7 @@ actions.delegate = async (payload) => { to, isSignedWithActiveKey, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' @@ -1379,7 +1379,7 @@ actions.undelegate = async (payload) => { from, isSignedWithActiveKey, } = payload; - let { quantity } = payload; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(symbol && typeof symbol === 'string' From b5bbf28a3e0ce758e90752ad3f3d2fa3e1c1aed9 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:26:53 -0400 Subject: [PATCH 05/12] format error --- contracts/tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index cf0aa050..70bf189f 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -624,7 +624,7 @@ actions.transferToContract = async (payload) => { const token = await api.db.findOne('tokens', { symbol }); // the symbol must exist - // then we need to check that the quantity is correct + // then we need to check that the quantity is correct. 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')) { From e67c82ace9012edc1d66283e96467e1d43cc11c7 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:29:08 -0400 Subject: [PATCH 06/12] format error 2 --- contracts/tokens.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index 70bf189f..66620f97 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -553,7 +553,7 @@ actions.transfer = async (payload) => { const { to, symbol, isSignedWithActiveKey, } = payload; - const { quantity } = payload; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(to && typeof to === 'string' @@ -624,7 +624,7 @@ actions.transferToContract = async (payload) => { const token = await api.db.findOne('tokens', { symbol }); // the symbol must exist - // then we need to check that the quantity is correct. + // then we need to check that the quantity is correct 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')) { From c4522a224b0ab777df1b18dc1a57d8b68798ccf3 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:37:02 -0400 Subject: [PATCH 07/12] update minify --- contracts/tokens_minify.js | 58 +------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/contracts/tokens_minify.js b/contracts/tokens_minify.js index 4f5ac5d6..998cccb0 100644 --- a/contracts/tokens_minify.js +++ b/contracts/tokens_minify.js @@ -1,57 +1 @@ -<<<<<<< HEAD -const FeeMethod = ['burn', 'issuer']; const PayoutType = ['user', 'contract']; function validateTokens(payTokenObj, voteTokenObj) { return !!api.assert(payTokenObj && (payTokenObj.issuer === api.sender || payTokenObj.symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" && api.sender === api.owner), 'must be issuer of payToken') && !!api.assert(voteTokenObj && voteTokenObj.stakingEnabled, 'voteToken must have staking enabled'); } function validateDateTime(str) { if (str.length === 24) { for (let i = 0; i < str.length && [5, 8, 11, 14, 17, 21].indexOf(i) === 1; i += 1) { const code = str.charCodeAt(i); if (!(code > 47 && code < 58)) return !1; } return !0; } return !1; } function validateDateRange(startDate, endDate, maxDays) { - if (!api.assert(validateDateTime(startDate) && validateDateTime(endDate), 'invalid datetime format: YYYY-MM-DDThh:mm:ss.sssZ')) return !1; const now = new Date(`${api.hiveBlockTimestamp}.000Z`); const start = new Date(startDate); const end = new Date(endDate); if (!api.assert(api.BigNumber(start.getTime()).lt(api.BigNumber(end.getTime()).minus(864e5)), 'dates must be at least 1 day apart') || !api.assert(api.BigNumber(start.getTime()).gt(api.BigNumber(now.getTime()).plus(864e5)), 'startDate must be at least 1 day in the future')) return !1; const rangeDays = api.BigNumber(start.getTime()).minus(end.getTime()).abs().dividedBy(864e5) - .toFixed(0, api.BigNumber.ROUND_CEIL); return !!api.assert(api.BigNumber(rangeDays).lte(maxDays), 'date range exceeds DTF maxDays'); -} function validateDateChange(proposal, newDate, maxDays) { - if (!api.assert(validateDateTime(newDate), 'invalid datetime format: YYYY-MM-DDThh:mm:ss.sssZ')) return !1; const start = new Date(proposal.startDate); const cur = new Date(proposal.endDate); const repl = new Date(newDate); if (!api.assert(api.BigNumber(start.getTime()).lt(api.BigNumber(repl.getTime()).minus(864e5)), 'dates must be at least 1 day apart')) return !1; if (!api.assert(repl <= cur, 'date can only be reduced')) return !1; const rangeDays = api.BigNumber(start.getTime()).minus(repl.getTime()).abs().dividedBy(864e5) - .toFixed(0, api.BigNumber.ROUND_CEIL); return !!api.assert(api.BigNumber(rangeDays).lte(maxDays), 'date range exceeds DTF maxDays'); -} function validatePending(proposal) { const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); return new Date(proposal.endDate) >= blockDate; } async function updateProposalWeight(id, deltaApprovalWeight, deltaToken = null) { const proposal = await api.db.findOne('proposals', { _id: id }); if (proposal && validatePending(proposal)) { if (deltaToken) { if ((await api.db.findOne('funds', { id: proposal.fundId })).voteToken !== deltaToken.symbol) return !0; } return proposal.approvalWeight = { $numberDecimal: api.BigNumber(proposal.approvalWeight.$numberDecimal).plus(deltaApprovalWeight) }, await api.db.update('proposals', proposal), !0; } return !1; } async function checkPendingProposals(dtf, params) { - const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.payToken }); const tickPayRatio = api.BigNumber(params.dtfTickHours).dividedBy(24); const funded = []; const fundedLog = []; let proposals; let offset = 0; let runningPay = api.BigNumber(dtf.maxAmountPerDay).times(tickPayRatio); for (;runningPay.gt(0);) { - proposals = await api.db.find('proposals', { - fundId: dtf.id, active: !0, approvalWeight: { $gt: { $numberDecimal: api.BigNumber(dtf.voteThreshold) } }, startDate: { $lte: blockDate.toISOString() }, endDate: { $gte: blockDate.toISOString() }, - }, params.processQueryLimit, offset, [{ index: 'byApprovalWeight', descending: !0 }, { index: '_id', descending: !1 }]); for (let i = 0; i < proposals.length; i += 1) { if (api.BigNumber(proposals[i].amountPerDay).times(tickPayRatio).gte(runningPay)) { proposals[i].tickPay = runningPay.toFixed(payTokenObj.precision, api.BigNumber.ROUND_DOWN), funded.push(proposals[i]), runningPay = api.BigNumber(0); break; }proposals[i].tickPay = api.BigNumber(proposals[i].amountPerDay).times(tickPayRatio).toFixed(payTokenObj.precision, api.BigNumber.ROUND_DOWN), funded.push(proposals[i]), runningPay = runningPay.minus(proposals[i].tickPay); } if (proposals.length < params.processQueryLimit) break; offset += params.processQueryLimit; - } for (let i = 0; i < funded.length; i += 1) { const fund = funded[i]; fundedLog.push({ id: fund._id, tickPay: fund.tickPay }), fund.payout.type === 'user' ? await api.executeSmartContract('tokens', 'issue', { to: fund.payout.name, symbol: payTokenObj.symbol, quantity: fund.tickPay }) : fund.payout.type === 'contract' && (await api.executeSmartContract('tokens', 'issueToContract', { to: fund.payout.name, symbol: payTokenObj.symbol, quantity: fund.tickPay }), await api.executeSmartContract(fund.payout.name, 'receiveDtfTokens', { data: fund.payout.contractPayload, symbol: payTokenObj.symbol, quantity: fund.tickPay })); }dtf.lastTickTime = api.BigNumber(blockDate.getTime()).toNumber(), await api.db.update('funds', dtf), api.emit('fundProposals', { fundId: dtf.id, funded: fundedLog }); -}actions.createSSC = async () => { - if (!1 === await api.db.tableExists('funds')) { - await api.db.createTable('funds', ['id', 'lastTickTime']), await api.db.createTable('proposals', ['fundId', { name: 'byApprovalWeight', index: { fundId: 1, approvalWeight: 1 } }]), await api.db.createTable('approvals', ['from', 'to']), await api.db.createTable('accounts', [], { primaryKey: ['account'] }), await api.db.createTable('params'); const params = { - dtfCreationFee: '1000', dtfUpdateFee: '300', dtfTickHours: '24', maxDtfsPerBlock: 40, maxAccountApprovals: 50, processQueryLimit: 1e3, - }; await api.db.insert('params', params); - } else { const params = await api.db.findOne('params', {}); if (!params.updateIndex) { const dtfs = await api.db.find('funds', {}); const voteTokens = new Set(); for (let i = 0; i < dtfs.length; i += 1)voteTokens.add(dtfs[i].voteToken); const resetAccounts = await api.db.find('accounts', {}); for (let i = 0; i < resetAccounts.length; i += 1) { const acct = resetAccounts[i]; acct.weights = acct.weights.filter(ele => voteTokens.has(ele.symbol)), await api.db.update('accounts', acct); } const resetProposals = await api.db.find('proposals', {}); for (let i = 0; i < resetProposals.length; i += 1) { const prop = resetProposals[i]; const propFund = dtfs.find(x => x.id === prop.fundId); const propApprovals = await api.db.find('approvals', { to: prop._id }); let newApprovalWeight = api.BigNumber('0'); for (let j = 0; j < propApprovals.length; j += 1) { const approval = propApprovals[j]; const approvalAcctWgt = resetAccounts.find(x => x.account === approval.from).weights.find(x => x.symbol === propFund.voteToken); newApprovalWeight = newApprovalWeight.plus(approvalAcctWgt.weight); }prop.approvalWeight = { $numberDecimal: newApprovalWeight }, await api.db.update('proposals', prop); }params.updateIndex = 1, await api.db.update('params', params); } } -}, actions.updateParams = async (payload) => { - const { - dtfCreationFee, dtfUpdateFee, dtfTickHours, maxDtfsPerBlock, maxAccountApprovals, processQueryLimit, - } = payload; if (api.sender !== api.owner) return; const params = await api.db.findOne('params', {}); if (dtfCreationFee) { if (!api.assert(typeof dtfCreationFee === 'string' && !api.BigNumber(dtfCreationFee).isNaN() && api.BigNumber(dtfCreationFee).gte(0), 'invalid dtfCreationFee')) return; params.dtfCreationFee = dtfCreationFee; } if (dtfUpdateFee) { if (!api.assert(typeof dtfUpdateFee === 'string' && !api.BigNumber(dtfUpdateFee).isNaN() && api.BigNumber(dtfUpdateFee).gte(0), 'invalid dtfUpdateFee')) return; params.dtfUpdateFee = dtfUpdateFee; } if (dtfTickHours) { if (!api.assert(typeof dtfTickHours === 'string' && api.BigNumber(dtfTickHours).isInteger() && api.BigNumber(dtfTickHours).gte(1), 'invalid dtfTickHours')) return; params.dtfTickHours = dtfTickHours; } if (maxDtfsPerBlock) { if (!api.assert(typeof maxDtfsPerBlock === 'string' && api.BigNumber(maxDtfsPerBlock).isInteger() && api.BigNumber(maxDtfsPerBlock).gte(1), 'invalid maxDtfsPerBlock')) return; params.maxDtfsPerBlock = api.BigNumber(maxDtfsPerBlock).toNumber(); } if (maxAccountApprovals) { if (!api.assert(typeof maxAccountApprovals === 'string' && api.BigNumber(maxAccountApprovals).isInteger() && api.BigNumber(maxAccountApprovals).gte(1), 'invalid maxDtfsPerBlock')) return; params.maxAccountApprovals = api.BigNumber(maxAccountApprovals).toNumber(); } if (processQueryLimit) { if (!api.assert(typeof processQueryLimit === 'string' && api.BigNumber(processQueryLimit).isInteger() && api.BigNumber(processQueryLimit).gte(1), 'invalid processQueryLimit')) return; params.processQueryLimit = api.BigNumber(processQueryLimit).toNumber(); } await api.db.update('params', params); -}, actions.createFund = async (payload) => { - const { - payToken, voteToken, voteThreshold, maxDays, maxAmountPerDay, proposalFee, isSignedWithActiveKey, - } = payload; const params = await api.db.findOne('params', {}); const { dtfCreationFee } = params; const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); const authorizedCreation = !(!api.BigNumber(dtfCreationFee).lte(0) && api.sender !== api.owner) || utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(dtfCreationFee); if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(typeof voteThreshold === 'string' && api.BigNumber(voteThreshold).gt(0), 'invalid voteThreshold: greater than 0') && api.assert(typeof maxDays === 'string' && api.BigNumber(maxDays).isInteger() && api.BigNumber(maxDays).gt(0) && api.BigNumber(maxDays).lte(730), 'invalid maxDays: integer between 1 and 730') && api.assert(typeof maxAmountPerDay === 'string' && api.BigNumber(maxAmountPerDay).gt(0), 'invalid maxAmountPerDay: greater than 0')) { - if (proposalFee) { if (!api.assert(typeof proposalFee === 'object' && typeof proposalFee.method === 'string' && FeeMethod.indexOf(proposalFee.method) !== -1 && typeof proposalFee.symbol === 'string' && typeof proposalFee.amount === 'string' && api.BigNumber(proposalFee.amount).gt(0), 'invalid proposalFee')) return; const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: proposalFee.symbol }); if (!api.assert(feeTokenObj && api.BigNumber(proposalFee.amount).dp() <= feeTokenObj.precision, 'invalid proposalFee token or precision')) return; } const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: payToken }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: voteToken }); if (!validateTokens(payTokenObj, voteTokenObj) || !api.assert(api.BigNumber(maxAmountPerDay).dp() <= payTokenObj.precision, 'maxAmountPerDay precision mismatch') || !api.assert(api.BigNumber(voteThreshold).dp() <= voteTokenObj.precision, 'voteThreshold precision mismatch')) return; const now = new Date(`${api.hiveBlockTimestamp}.000Z`); const newDtf = { - payToken, voteToken, voteThreshold, maxDays, maxAmountPerDay, proposalFee, active: !1, creator: api.sender, lastTickTime: now.getTime(), - }; newDtf.id = `${payToken}:${voteToken}`; const existingDtf = await api.db.findOne('funds', { id: newDtf.id }); if (!api.assert(!existingDtf, 'DTF already exists')) return; const insertedDtf = await api.db.insert('funds', newDtf); api.sender !== api.owner && api.sender !== 'null' && api.BigNumber(dtfCreationFee).gt(0) && await api.executeSmartContract('tokens', 'transfer', { - to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: dtfCreationFee, isSignedWithActiveKey, - }), api.emit('createFund', { id: insertedDtf.id }); - } -}, actions.updateFund = async (payload) => { - const { - fundId, voteThreshold, maxDays, maxAmountPerDay, proposalFee, isSignedWithActiveKey, - } = payload; const params = await api.db.findOne('params', {}); const { dtfUpdateFee } = params; const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); const authorizedUpdate = !(!api.BigNumber(dtfUpdateFee).lte(0) && api.sender !== api.owner) || utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(dtfUpdateFee); if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(typeof voteThreshold === 'string' && api.BigNumber(voteThreshold).gt(0), 'invalid voteThreshold: greater than 0') && api.assert(typeof maxDays === 'string' && api.BigNumber(maxDays).isInteger() && api.BigNumber(maxDays).gt(0) && api.BigNumber(maxDays).lte(730), 'invalid maxDays: integer between 1 and 730') && api.assert(typeof maxAmountPerDay === 'string' && api.BigNumber(maxAmountPerDay).gt(0), 'invalid maxAmountPerDay: greater than 0')) { - if (proposalFee) { if (!api.assert(typeof proposalFee === 'object' && typeof proposalFee.method === 'string' && FeeMethod.indexOf(proposalFee.method) !== -1 && typeof proposalFee.symbol === 'string' && typeof proposalFee.amount === 'string' && api.BigNumber(proposalFee.amount).gt(0), 'invalid proposalFee')) return; const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: proposalFee.symbol }); if (!api.assert(feeTokenObj && api.BigNumber(proposalFee.amount).dp() <= feeTokenObj.precision, 'invalid proposalFee token or precision')) return; } const existingDtf = await api.db.findOne('funds', { id: fundId }); if (!api.assert(existingDtf, 'DTF not found') || !api.assert(existingDtf.creator === api.sender || api.owner === api.sender, 'must be DTF creator')) return; const payTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingDtf.payToken }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingDtf.voteToken }); if (!api.assert(api.BigNumber(maxAmountPerDay).dp() <= payTokenObj.precision, 'maxAmountPerDay precision mismatch') || !api.assert(api.BigNumber(voteThreshold).dp() <= voteTokenObj.precision, 'voteThreshold precision mismatch')) return; existingDtf.voteThreshold = voteThreshold, existingDtf.maxDays = maxDays, existingDtf.maxAmountPerDay = maxAmountPerDay, proposalFee && (existingDtf.proposalFee = proposalFee), await api.db.update('funds', existingDtf), api.sender !== api.owner && api.sender !== 'null' && api.BigNumber(dtfUpdateFee).gt(0) && await api.executeSmartContract('tokens', 'transfer', { - to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: dtfUpdateFee, isSignedWithActiveKey, - }), api.emit('updateFund', { id: fundId }); - } -}, actions.setDtfActive = async (payload) => { const { fundId, active, isSignedWithActiveKey } = payload; if (!api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key')) return; const dtf = await api.db.findOne('funds', { id: fundId }); api.assert(dtf, 'DTF does not exist') && api.assert(dtf.creator === api.sender || api.owner === api.sender, 'must be DTF creator') && (dtf.active = !!active, await api.db.update('funds', dtf), api.emit('setDtfActive', { id: dtf.id, active: dtf.active })); }, actions.createProposal = async (payload) => { - const { - fundId, title, startDate, endDate, amountPerDay, authorPermlink, payout, isSignedWithActiveKey, - } = payload; const dtf = await api.db.findOne('funds', { id: fundId }); if (!api.assert(dtf, 'DTF does not exist')) return; let authorizedCreation = !0; if (dtf.proposalFee) { const feeTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.proposalFee.symbol }); authorizedCreation = !(!api.BigNumber(dtf.proposalFee.amount).lte(0) && api.sender !== api.owner) || feeTokenBalance && api.BigNumber(feeTokenBalance.balance).gte(dtf.proposalFee.amount); } if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(!0 === dtf.active, 'DTF is not active') && api.assert(typeof title === 'string' && title.length > 0 && title.length <= 80, 'invalid title: between 1 and 80 characters') && api.assert(typeof authorPermlink === 'string' && authorPermlink.length > 0 && authorPermlink.length <= 255, 'invalid authorPermlink: between 1 and 255 characters') && api.assert(typeof amountPerDay === 'string' && api.BigNumber(amountPerDay).isInteger() && api.BigNumber(amountPerDay).gt(0), 'invalid amountPerDay: greater than 0') && api.assert(api.BigNumber(amountPerDay).lte(dtf.maxAmountPerDay), 'invalid amountPerDay: exceeds DTF maxAmountPerDay') && api.assert(typeof payout === 'object' && typeof payout.type === 'string' && PayoutType.indexOf(payout.type) !== -1 && (payout.type !== 'contract' || typeof payout.contractPayload === 'object') && typeof payout.name === 'string' && payout.name.length >= 3 && payout.name.length <= 50, 'invalid payout settings') && validateDateRange(startDate, endDate, dtf.maxDays)) { - const newProposal = { - fundId, title, startDate, endDate, amountPerDay, authorPermlink, payout, creator: api.sender, approvalWeight: { $numberDecimal: '0' }, active: !0, - }; const insertedProposal = await api.db.insert('proposals', newProposal); if (api.sender !== api.owner && dtf.proposalFee) if (dtf.proposalFee.method === 'burn') await api.executeSmartContract('tokens', 'transfer', { to: 'null', symbol: dtf.proposalFee.symbol, quantity: dtf.proposalFee.amount }); else if (dtf.proposalFee.method === 'issuer') { const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.proposalFee.symbol }); await api.executeSmartContract('tokens', 'transfer', { to: feeTokenObj.issuer, symbol: dtf.proposalFee.symbol, quantity: dtf.proposalFee.amount }); }api.emit('createProposal', { id: insertedProposal._id }); - } -}, actions.updateProposal = async (payload) => { - const { - id, title, endDate, amountPerDay, authorPermlink, isSignedWithActiveKey, - } = payload; if (!api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) return; const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (!api.assert(proposal, 'proposal does not exist') || !api.assert(proposal.creator === api.sender || api.owner === api.sender, 'must be proposal creator')) return; const dtf = await api.db.findOne('funds', { id: proposal.fundId, active: !0 }); api.assert(dtf, 'DTF does not exist or inactive') && api.assert(!0 === isSignedWithActiveKey, 'you must use a transaction signed with your active key') && api.assert(!0 === dtf.active, 'DTF is not active') && api.assert(!0 === proposal.active, 'proposal is not active') && api.assert(typeof title === 'string' && title.length > 0 && title.length <= 80, 'invalid title: between 1 and 80 characters') && api.assert(typeof authorPermlink === 'string' && authorPermlink.length > 0 && authorPermlink.length <= 255, 'invalid authorPermlink: between 1 and 255 characters') && api.assert(typeof amountPerDay === 'string' && api.BigNumber(amountPerDay).isInteger() && api.BigNumber(amountPerDay).gt(0) && api.BigNumber(amountPerDay).lte(proposal.amountPerDay), 'invalid amountPerDay: greater than 0 and cannot be increased') && api.assert(api.BigNumber(amountPerDay).lte(dtf.maxAmountPerDay), 'invalid amountPerDay: exceeds DTF maxAmountPerDay') && validateDateChange(proposal, endDate, dtf.maxDays) && (proposal.title = title, proposal.endDate = endDate, proposal.amountPerDay = amountPerDay, proposal.authorPermlink = authorPermlink, await api.db.update('proposals', proposal), api.emit('updateProposal', { id: proposal._id })); -}, actions.disableProposal = async (payload) => { const { id, isSignedWithActiveKey } = payload; if (!api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) return; const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); api.assert(proposal, 'proposal does not exist') && api.assert(!0 === proposal.active, 'proposal already disabled') && api.assert(proposal.creator === api.sender || api.owner === api.sender, 'must be proposal creator') && (proposal.active = !1, await api.db.update('proposals', proposal), api.emit('disableProposal', { id: proposal._id })); }, actions.approveProposal = async (payload) => { const { id } = payload; const params = await api.db.findOne('params', {}); if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (api.assert(proposal, 'proposal does not exist') && api.assert(validatePending(proposal), 'proposal is not pending')) { const dtf = await api.db.findOne('funds', { id: proposal.fundId }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.voteToken }); let acct = await api.db.findOne('accounts', { account: api.sender }); acct === null && (acct = { account: api.sender, weights: [] }, acct = await api.db.insert('accounts', acct)); let activeApprovals = 0; const approvals = await api.db.find('approvals', { from: api.sender, proposalPending: !0 }, params.maxAccountApprovals, 0, [{ index: '_id', descending: !0 }]); for (let index = 0; index < approvals.length; index += 1) { const approval = approvals[index]; const approvalProposal = await api.db.findOne('proposals', { _id: approval.to }); approvalProposal && validatePending(approvalProposal) ? activeApprovals += 1 : (approval.proposalPending = !1, await api.db.update('approvals', approval)); } if (!api.assert(activeApprovals < params.maxAccountApprovals, `you can only approve ${params.maxAccountApprovals} active proposals`)) return; let approval = await api.db.findOne('approvals', { from: api.sender, to: proposal._id }); if (api.assert(approval === null, 'you already approved this proposal')) { approval = { from: api.sender, to: proposal._id, proposalPending: !0 }, await api.db.insert('approvals', approval); const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.voteToken }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)); const wIndex = acct.weights.findIndex(x => x.symbol === dtf.voteToken); wIndex !== -1 ? acct.weights[wIndex].weight = approvalWeight : acct.weights.push({ symbol: dtf.voteToken, weight: approvalWeight }), await api.db.update('accounts', acct), await updateProposalWeight(proposal._id, approvalWeight), api.emit('approveProposal', { id: proposal._id }); } } } }, actions.disapproveProposal = async (payload) => { const { id } = payload; if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { const proposal = await api.db.findOne('proposals', { _id: api.BigNumber(id).toNumber() }); if (api.assert(proposal, 'proposal does not exist') && api.assert(validatePending(proposal), 'proposal is not pending')) { const dtf = await api.db.findOne('funds', { id: proposal.fundId }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: dtf.voteToken }); let acct = await api.db.findOne('accounts', { account: api.sender }); acct === null && (acct = { account: api.sender, weights: [] }, acct = await api.db.insert('accounts', acct)); const approval = await api.db.findOne('approvals', { from: api.sender, to: proposal._id }); if (api.assert(approval !== null, 'you have not approved this proposal')) { await api.db.remove('approvals', approval); const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: dtf.voteToken }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)); const wIndex = acct.weights.findIndex(x => x.symbol === dtf.voteToken); wIndex !== -1 ? acct.weights[wIndex].weight = approvalWeight : acct.weights.push({ symbol: dtf.voteToken, weight: approvalWeight }), await api.db.update('accounts', acct), await updateProposalWeight(proposal._id, api.BigNumber(approvalWeight).negated()), api.emit('disapproveProposal', { id: proposal._id }); } } } }, actions.updateProposalApprovals = async (payload) => { const { account, token, callingContractInfo } = payload; if (void 0 === callingContractInfo) return; if (callingContractInfo.name !== 'tokens') return; const acct = await api.db.findOne('accounts', { account }); if (acct !== null) { const params = await api.db.findOne('params', {}); const wIndex = acct.weights.findIndex(x => x.symbol === token.symbol); if (wIndex !== -1) { const balance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: token.symbol }); let approvalWeight = 0; balance && balance.stake && (approvalWeight = balance.stake), balance && balance.delegationsIn && (approvalWeight = api.BigNumber(approvalWeight).plus(balance.delegationsIn).toFixed(token.precision, api.BigNumber.ROUND_HALF_UP)); let oldApprovalWeight = 0; oldApprovalWeight = acct.weights[wIndex].weight, acct.weights[wIndex].weight = approvalWeight; const deltaApprovalWeight = api.BigNumber(approvalWeight).minus(oldApprovalWeight).dp(token.precision, api.BigNumber.ROUND_HALF_UP); if (!api.BigNumber(deltaApprovalWeight).eq(0)) { await api.db.update('accounts', acct); const approvals = await api.db.find('approvals', { from: account, proposalPending: !0 }, params.maxAccountApprovals, 0, [{ index: '_id', descending: !0 }]); for (let index = 0; index < approvals.length; index += 1) { const approval = approvals[index]; await updateProposalWeight(approval.to, deltaApprovalWeight, token) || (approval.proposalPending = !1, await api.db.update('approvals', approval)); } } } } }, actions.checkPendingDtfs = async () => { if (api.assert(api.sender === 'null', 'not authorized')) { const params = await api.db.findOne('params', {}); const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const tickTime = api.BigNumber(blockDate.getTime()).minus(3600 * params.dtfTickHours * 1e3).toNumber(); const pendingDtfs = await api.db.find('funds', { active: !0, lastTickTime: { $lte: tickTime } }, params.maxDtfsPerBlock, 0, [{ index: 'lastTickTime', descending: !1 }, { index: '_id', descending: !1 }]); for (let i = 0; i < pendingDtfs.length; i += 1) await checkPendingProposals(pendingDtfs[i], params); } }; -======= -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","burndollar"],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=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),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token})),await api.db.update("balances",balance),await api.db.update("tokens",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>>>>>> refs/remotes/origin/qa +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","burndollar"],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=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),"'${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}$'"===symbol&&await api.executeSmartContract("witnesses","updateWitnessesApprovals",{account:account}),await api.executeSmartContract("mining","handleStakeChange",{account:account,symbol:symbol,quantity:api.BigNumber(nextTokensToRelease).negated()}),await api.executeSmartContract("tokenfunds","updateProposalApprovals",{account:account,token:token})),await api.db.update("balances",balance),await api.db.update("tokens",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 Date: Mon, 14 Jul 2025 15:40:55 -0400 Subject: [PATCH 08/12] minify2 --- contracts/tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index 66620f97..983d0433 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -553,7 +553,7 @@ actions.transfer = async (payload) => { const { to, symbol, isSignedWithActiveKey, } = payload; - let quantity = payload.quantity; + let quantity = payload.quantity; if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(to && typeof to === 'string' From ee26cc0c215c42f5a4ba6fb27d95967a10ae4884 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:41:40 -0400 Subject: [PATCH 09/12] 2 --- contracts/tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index 983d0433..80808e8e 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -337,7 +337,7 @@ actions.transferOwnership = async (payload) => { token.issuer = finalTo; await api.db.update('tokens', token); api.emit('transferOwnership', { - from: api.sender, to: finalTo, symbol, + from: api.sender, to: finalTo, symbol, }); } } From e1df0410ad5edf06fa27894d832d823fe8bb9421 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:42:12 -0400 Subject: [PATCH 10/12] 1 --- contracts/tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index 80808e8e..97a27dc3 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -337,7 +337,7 @@ actions.transferOwnership = async (payload) => { token.issuer = finalTo; await api.db.update('tokens', token); api.emit('transferOwnership', { - from: api.sender, to: finalTo, symbol, + from: api.sender, to: finalTo, symbol, }); } } From bddf7a2b1eb1fff4270cf24128103a5b06aedf76 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:42:39 -0400 Subject: [PATCH 11/12] 1 --- contracts/tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index 97a27dc3..401dd881 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -337,7 +337,7 @@ actions.transferOwnership = async (payload) => { token.issuer = finalTo; await api.db.update('tokens', token); api.emit('transferOwnership', { - from: api.sender, to: finalTo, symbol, + from: api.sender, to: finalTo, symbol }); } } From e22b8f7c26d0f06519fa3689e579fb2b04b263a2 Mon Sep 17 00:00:00 2001 From: BostonTechie Date: Mon, 14 Jul 2025 15:45:25 -0400 Subject: [PATCH 12/12] all done --- contracts/burndollar_minify.js | 72 +--------------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/contracts/burndollar_minify.js b/contracts/burndollar_minify.js index f2df1999..c3ac0d87 100644 --- a/contracts/burndollar_minify.js +++ b/contracts/burndollar_minify.js @@ -1,71 +1 @@ -<<<<<<< HEAD -const stablePairArray = ['SWAP.HBD', 'SWAP.USDT', 'SWAP.DAI', 'SWAP.USDC']; const countDecimals = value => api.BigNumber(value).dp(); const verifyTokenCreation = async symbolFind => !!await api.db.findOneInTable('tokens', 'tokens', { symbol: symbolFind }); const verifyTokenBalance = async (account, amount, symbolFind) => { const findTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: symbolFind }); return !(!findTokenBalance || !api.BigNumber(findTokenBalance.balance).gte(amount)); }; const checkStablePosition = (tokenPair) => { const [firstToken, secondToken] = tokenPair.split(':'); return stablePairArray.includes(firstToken) ? 'base' : !!stablePairArray.includes(secondToken) && 'quote'; }; const findMarketPools = async (parentSymbol, toggle) => { - const childSymbol = `${parentSymbol}.D`; let poolData; if (toggle === 'stable') { const stableResults = [`${parentSymbol}`, `${childSymbol}`].flatMap(pElement => stablePairArray.flatMap(sElement => [`${sElement}:${pElement}`, `${pElement}:${sElement}`])); const 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 (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 = {}; return name.includes(firstToken) && (quoteOrBasePosition = 'base'), name.includes(secondToken) && (quoteOrBasePosition = 'quote'), quoteOrBasePosition && quoteOrBasePosition === 'base' ? (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, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, - }) : quoteOrBasePosition && quoteOrBasePosition === 'quote' && (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, poolValueUSD: fullPoolinUSD, parentPrice: parentTokenPrice, - }), returnObject; -}; const isTokenTransferVerified = (result, from, to, symbol, quantity, eventStr) => !(void 0 !== result.errors || !result.events || void 0 === 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)); 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 !1; - } 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, - }); 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, 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), updateParamsFee && typeof updateParamsFee === 'string' && !api.BigNumber(updateParamsFee).isNaN() && api.BigNumber(updateParamsFee).gte(1) && (params.updateParamsFee = updateParamsFee), burnUsageFee && typeof burnUsageFee === 'string' && !api.BigNumber(burnUsageFee).isNaN() && api.BigNumber(burnUsageFee).gte(1) && (params.burnUsageFee = burnUsageFee), minAmountConvertible && typeof minAmountConvertible === 'string' && !minAmountConvertible.isNaN() && minAmountConvertible.gte(1) && (params.minAmountConvertible = minAmountConvertible), dTokenToIssuer && typeof dTokenToIssuer === 'string' && !api.BigNumber(dTokenToIssuer).isNaN() && api.BigNumber(dTokenToIssuer).gte(1) && (params.dTokenToIssuer = dTokenToIssuer), burnToken && typeof burnToken === 'string') { await api.db.findOneInTable('tokens', 'tokens', { symbol: burnToken }) && (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(!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 && 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 = 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 && 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 !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, - }), 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 = 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 && 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, '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', {}); 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); 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, - }), api.emit('updated params', { symbol, burnRouting, feePercentage })); - } - } -}, actions.convert = async (payload) => { - const { symbol, quantity, isSignedWithActiveKey } = payload; if (api.assert(!0 === isSignedWithActiveKey, '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); const 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 && 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); const 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'; let 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, feeRouting: parentPairParams.burnRouting, parentSymbol: keyname, precision: parentPairParams.precision, childIssued: xxxdToIssue, parentPriceInUSD: calcResultParentPool.parentPrice, - }); - } - } - } - } -}; -======= -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})}}}}}; ->>>>>>> refs/remotes/origin/qa +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