Skip to content

Commit

Permalink
Coupon and Mainnet balance fixes (#130)
Browse files Browse the repository at this point in the history
* Development (mainnet balance check) (#128)

* mainnet balance checks

* cfg

* logs

* typo

* json

* nits

* update prod config (#129)

* exclude config field

* checks fix

* nits
  • Loading branch information
rajranjan0608 authored Dec 27, 2023
1 parent b9c6e6d commit 10e97e3
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 42 deletions.
71 changes: 32 additions & 39 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
SendTokenResponse,
ChainType,
EVMInstanceAndConfig,
CouponValidity
} from './types'

import {
Expand All @@ -22,7 +21,13 @@ import {
DEBUG,
} from './config.json'
import { CouponService } from './CouponService/couponService'
import { checkMainnetBalance } from './utils/mainnetBalanceCheck'
import {
PIPELINE_CHECKS,
PipelineCheckValidity,
checkCouponPipeline,
checkMainnetBalancePipeline,
pipelineFailureMessage
} from './utils/pipelineChecks'

dotenv.config()

Expand Down Expand Up @@ -73,11 +78,13 @@ const getChainByID = (chains: ChainType[], id: string): ChainType | undefined =>
return reply
}

const separateConfigFields = ['COUPON_REQUIRED', 'MAINNET_BALANCE_CHECK_RPC']

// Populates the missing config keys of the child using the parent's config
const populateConfig = (child: any, parent: any): any => {
Object.keys(parent || {}).forEach((key) => {
// Do not copy COUPON config (in ERC20 tokens) from host chain
if(key !== 'COUPON_REQUIRED' && !child[key]) {
// Do not copy configs of separateConfigFields (in ERC20 tokens) from host chain
if(!separateConfigFields.includes(key) && !child[key]) {
child[key] = parent[key]
}
})
Expand Down Expand Up @@ -130,46 +137,32 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
const dripAmount = erc20Instance?.config.DRIP_AMOUNT ?? evm.config.DRIP_AMOUNT

/**
* MAINNET BALANCE OR COUPON VALIDATION checks
* 1. If mainnet balance check is enabled, users would be required to have mainnet balance
* 2. If coupon validation is enabled, users would need a specific coupon id to get tokens
* 3. If both are enabled, then any one would be sufficient
* Pipeline Checks
* 1. Pipelines are checks or rules that a request goes through before being processed
* 2. The request should pass at least one pipeline check
* 3. If no pipeline check is required for a token, then directly process the request
* 4. Currently, we have 2 pipeline checks: Coupon Check & Mainnet Balance Check
*/
const mainnetCheckEnabledRPC = (erc20Instance ? erc20Instance.config.MAINNET_BALANCE_CHECK_RPC : evm.config.MAINNET_BALANCE_CHECK_RPC) ?? false
const couponCheckEnabled = couponConfig.IS_ENABLED && ((erc20Instance ? erc20Instance.config.COUPON_REQUIRED : evm.config.COUPON_REQUIRED) ?? false)

// mainnet balance checks
const mainnetCheckEnabledRPC = erc20Instance?.config.MAINNET_BALANCE_CHECK_RPC ?? evm.config.MAINNET_BALANCE_CHECK_RPC ?? false
let mainnetCheckPassed = false
if (mainnetCheckEnabledRPC && (await checkMainnetBalance(faucetConfigId, mainnetCheckEnabledRPC, address))) {
mainnetCheckPassed = true
}
let pipelineValidity: PipelineCheckValidity = {isValid: false, dripAmount}
!pipelineValidity.isValid && couponCheckEnabled && await checkCouponPipeline(couponService, pipelineValidity, faucetConfigId, coupon)

// don't check mainnet balance, if coupon is provided
!pipelineValidity.isValid && !coupon && mainnetCheckEnabledRPC && await checkMainnetBalancePipeline(pipelineValidity, faucetConfigId, mainnetCheckEnabledRPC, address)

// validate coupon
let couponValidity: CouponValidity = {isValid: false, amount: dripAmount}
if (
// check coupon validation only if mainnet check failed (either no-balance or check not enabled)
!mainnetCheckPassed &&

// coupon checks
couponConfig.IS_ENABLED &&
// if request is for erc20 tokens
((erc20Instance && erc20Instance.config.COUPON_REQUIRED) ||
// if request is for evm native token
(erc20Instance === undefined && evm.config.COUPON_REQUIRED))
(mainnetCheckEnabledRPC || couponCheckEnabled) &&
!pipelineValidity.isValid
) {
// if coupon is required but not passed in request
if (coupon === undefined) {
res.status(400).send({message: "Coupon is required for this chain or token!"})
return
}
couponValidity = await couponService.consumeCouponAmount(coupon, faucetConfigId, dripAmount)
if (!couponValidity.isValid) {
res.status(400).send({message: "Invalid or expired coupon provided. Contact support team on Discord!"})
return
}
// failed
res.status(400).send({message: pipelineValidity.errorMessage + pipelineFailureMessage(mainnetCheckEnabledRPC, couponCheckEnabled)})
return
}

// logging requests (if enabled)
DEBUG && console.log("New faucet request:", JSON.stringify({
DEBUG && console.log(JSON.stringify({
"type": "NewFaucetRequest",
"address": address,
"chain": chain,
Expand All @@ -178,12 +171,12 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
}))

// send request
evm.instance.sendToken(address, erc20, couponValidity.amount, async (data: SendTokenResponse) => {
evm.instance.sendToken(address, erc20, pipelineValidity.dripAmount, async (data: SendTokenResponse) => {
const { status, message, txHash } = data

// reclaim coupon if transaction is failed
if (coupon && couponValidity.isValid && txHash === undefined) {
await couponService.reclaimCouponAmount(coupon, dripAmount)
if (pipelineValidity.checkPassedType === PIPELINE_CHECKS.COUPON && coupon && txHash === undefined) {
await couponService.reclaimCouponAmount(coupon, pipelineValidity.dripAmount)
}
res.status(status).send({message, txHash})
})
Expand Down
7 changes: 4 additions & 3 deletions utils/mainnetBalanceCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export async function checkMainnetBalance(faucetConfigId: string, rpc: string, a
})
const balance = parseInt(response.data.result)
if (balance > threshold) {
console.log("Successful FaucetMainnetBalanceCheck:", JSON.stringify({
console.log(JSON.stringify({
type: "FaucetMainnetBalanceCheckSuccess",
chain: faucetConfigId,
address: address,
balance: balance,
address,
balance,
rpc,
}))
return true
}
Expand Down
58 changes: 58 additions & 0 deletions utils/pipelineChecks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { CouponService } from '../CouponService/couponService'
import { checkMainnetBalance } from './mainnetBalanceCheck'

export enum PIPELINE_CHECKS {
MAINNET_BALANCE = 'mainnet_check',
COUPON = 'coupon_check'
}

export type PipelineCheckValidity = {
isValid: boolean,
checkPassedType?: PIPELINE_CHECKS,
errorMessage?: string,
dripAmount: number,
}

export function pipelineFailureMessage(mainnetBalanceCheckEnabled: boolean | string, couponCheckEnabled: boolean): string {
if (mainnetBalanceCheckEnabled && couponCheckEnabled) {
return "Either mainnet balance or a valid coupon is required!"
} else if (mainnetBalanceCheckEnabled) {
return "Mainnet balance is required!"
} else if (couponCheckEnabled) {
return "A valid coupon is required!"
}

return ""
}

export async function checkCouponPipeline(
couponService: CouponService,
pipelineCheckValidity: PipelineCheckValidity,
faucetConfigId: string,
coupon?: string,
) {
// if coupon is required but not passed in request
if (coupon === undefined) {
return
}

const couponValidity = await couponService.consumeCouponAmount(coupon, faucetConfigId, pipelineCheckValidity.dripAmount)
if (!couponValidity.isValid) {
pipelineCheckValidity.errorMessage = "Invalid or expired coupon provided. Contact support team on Discord! "
return
} else {
pipelineCheckValidity.isValid = true
pipelineCheckValidity.dripAmount = couponValidity.amount
pipelineCheckValidity.checkPassedType = PIPELINE_CHECKS.COUPON
}
}

export async function checkMainnetBalancePipeline(pipelineCheckValidity: PipelineCheckValidity, faucetConfigId: string, rpc: string, address: string) {
const isValid = await checkMainnetBalance(faucetConfigId, rpc, address)
if (isValid) {
pipelineCheckValidity.isValid = true
pipelineCheckValidity.checkPassedType = PIPELINE_CHECKS.MAINNET_BALANCE
} else {
pipelineCheckValidity.errorMessage = "Mainnet balance check failed! "
}
}

0 comments on commit 10e97e3

Please sign in to comment.