UNPKG

cu8-lucky-draw-pool-engine

Version:
204 lines (183 loc) 6 kB
import dayjs, { OpUnitType, QUnitType } from 'dayjs'; import mongoose, { Connection } from 'mongoose'; import PoolSchema from './models/Pool.schema'; import { ERRORS, PoolConfig, PoolStatus, Reward } from './types'; import { randomInt } from './utils'; //@ts-ignore; import bezier from 'cubic-bezier'; const init = async ({ mongoUrl, debug, cleanUpPools, }: { mongoUrl: string; debug?: boolean; cleanUpPools?: boolean; }) => { let db: Connection; const RATE_DEMICAL_PLACES = 2; const RATE_DEMICAL_PLACES_MULTIPLIER = Math.pow(RATE_DEMICAL_PLACES, 10); try { db = mongoose.createConnection(mongoUrl); } catch (e) { throw new Error(ERRORS.ERR_DB_CONN); } const Pool = db.model('Pool', PoolSchema, 'Pool'); if (cleanUpPools) { await Pool.deleteMany({}); } const listPools = async () => { const pools = await Pool.find(); return pools; }; const createPool = async ({ poolKey, name, startTime, endTime, period, initialAmount, config, data, }: { poolKey: string; name: string; startTime: Date; endTime: Date; period?: QUnitType | OpUnitType; initialAmount: number; config?: PoolConfig; data?: { [key: string]: any }; }) => { const pool = await Pool.create({ poolKey, name, startTime, endTime, period, initialAmount, unclaimed: initialAmount, status: PoolStatus.ACTIVE, config, data, }); return pool; }; const disablePool = async (poolKey: string) => { await Pool.updateOne({ poolKey }, { $set: { status: PoolStatus.DISABLED, disabledAt: Date.now() } }); }; const enablePool = async (poolKey: string) => { await Pool.updateOne({ poolKey }, { $set: { status: PoolStatus.ACTIVE } }); }; const draw = async ({ poolKeys, accessRate, timestamp, }: { poolKeys: string[]; accessRate?: number; timestamp?: Date; }) => { const handlingData: { [key: string]: any } = {}; handlingData.accessRate = 100; if (accessRate || accessRate === 0) { handlingData.accessRate = accessRate; } handlingData.accessRollResult = randomInt(0, 100 * RATE_DEMICAL_PLACES_MULTIPLIER - 1); handlingData.poolsAccessed = handlingData.accessRollResult < handlingData.accessRate * RATE_DEMICAL_PLACES_MULTIPLIER; if (!handlingData.poolsAccessed) { if (debug) { console.log(handlingData); } return; } const currentTime = dayjs(timestamp); let reward: Reward | undefined = undefined; const pools = await Pool.find({ poolKey: { $in: poolKeys }, status: PoolStatus.ACTIVE }); handlingData.pools = []; for (const pool of pools) { const handlingPoolData: { [key: string]: any } = { poolKey: pool.poolKey }; let startTime = dayjs(pool.startTime); let endTime = dayjs(pool.endTime); let maxRate = 100; let minRate = 0; if (pool.config?.maxRate !== undefined) { maxRate = pool.config.maxRate; } if (pool.config?.minRate !== undefined) { minRate = pool.config.minRate; } handlingPoolData.burntAmount = pool.initialAmount - pool.unclaimed; handlingPoolData.rate = 0; const unit: OpUnitType | QUnitType = (pool.period as any) || 'ms'; handlingPoolData.expectedBurnSpeed = endTime.diff(startTime, unit, true) / pool.initialAmount; handlingPoolData.expectedBurnAmount = (currentTime.diff(startTime, unit) + 1) / handlingPoolData.expectedBurnSpeed; handlingPoolData.remaining = handlingPoolData.expectedBurnAmount - handlingPoolData.burntAmount; // console.log('duration', endTime.diff(startTime, unit, true)); // console.log('burn speed', handlingPoolData.expectedBurnSpeed); // console.log(currentTime.diff(startTime, unit)); // console.log('expected', handlingPoolData.expectedBurnAmount); // console.log(handlingPoolData.remaining); if (handlingPoolData.remaining <= 0) { handlingData.pools.push(handlingPoolData); continue; } handlingPoolData.controlPoints = pool.config?.controlPoints || [ [0, 0], [1, 1], ]; let rateFormula = bezier( handlingPoolData.controlPoints[0][0], handlingPoolData.controlPoints[0][1], handlingPoolData.controlPoints[1][0], handlingPoolData.controlPoints[1][1], 1000 / 60 / handlingPoolData.expectedBurnSpeed / 4 ); const rate = ((maxRate - minRate) * rateFormula(handlingPoolData.expectedBurnAmount - handlingPoolData.burntAmount) + minRate) * RATE_DEMICAL_PLACES_MULTIPLIER; handlingPoolData.rate = rate / RATE_DEMICAL_PLACES_MULTIPLIER; handlingPoolData.rollResult = randomInt(0, 100 * RATE_DEMICAL_PLACES_MULTIPLIER - 1); handlingPoolData.poolWinned = handlingPoolData.rollResult < rate; if (!handlingPoolData.poolWinned) { handlingData.pools.push(handlingPoolData); continue; } try { const updatedPool = await Pool.findOneAndUpdate( { poolKey: pool.poolKey, unclaimed: { $gte: 1, $eq: pool.unclaimed }, }, { $inc: { unclaimed: -1 } }, { new: true } ); if (!updatedPool) { handlingData.pools.push(handlingPoolData); continue; } else { reward = { poolName: updatedPool.name, poolKey: updatedPool.poolKey, poolData: updatedPool.data, }; handlingData.pools.push(handlingPoolData); break; } } catch (e: any) { handlingPoolData.error = e.message; handlingData.pools.push(handlingPoolData); } } if (debug) { console.log(JSON.stringify(handlingData, null, 2)); } return reward; }; return { listPools, createPool, enablePool, disablePool, draw }; }; export { init }; export default init;