Skip to content

Commit

Permalink
cleanup and custom coupon amount
Browse files Browse the repository at this point in the history
  • Loading branch information
rajranjan0608 committed Nov 22, 2023
1 parent c063bb3 commit 82b07ec
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 46 deletions.
20 changes: 14 additions & 6 deletions CouponService/couponService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient, UpdateCommand, ScanCommand } from "@aws-sdk/lib-dynamodb"
import { Mutex, MutexInterface } from 'async-mutex'
import { CouponValidity } from "../types"

type Coupon = {
id: string,
faucetConfigId: string,
maxLimitAmount: number,
consumedAmount: number,
expiry: number,
amountPerCoupon: number,

}

type CouponConfig = {
Expand All @@ -17,6 +21,7 @@ type CouponConfig = {
function validateCouponData(coupon: any, couponConfig: CouponConfig): Coupon | undefined {
if (
coupon.id &&
coupon.faucetConfigId &&
coupon.maxLimitAmount > 0 &&
coupon.maxLimitAmount <= couponConfig.MAX_LIMIT_CAP &&
coupon.consumedAmount <= coupon.maxLimitAmount &&
Expand Down Expand Up @@ -115,22 +120,25 @@ export class CouponService {
})
}

async consumeCouponAmount(id: string, amount: number): Promise<boolean> {
async consumeCouponAmount(id: string, faucetConfigId: string, amount: number): Promise<CouponValidity> {
// Return `true` early, if coupon system is disabled (for debugging)
if (!this.couponConfig.IS_ENABLED) return true
if (!this.couponConfig.IS_ENABLED) return { isValid: true, amount }

const release = await this.mutex.acquire()
try {
const coupon = this.coupons.get(id)
const couponAmount = coupon?.amountPerCoupon ?? amount
if (
coupon &&
coupon.faucetConfigId === faucetConfigId &&
coupon.expiry > (Date.now() / 1000) &&
coupon.consumedAmount + amount < coupon.maxLimitAmount
coupon.consumedAmount + couponAmount < coupon.maxLimitAmount
) {
coupon.consumedAmount += amount
return true
console.log(coupon, couponAmount)
coupon.consumedAmount += couponAmount
return { isValid: true, amount: couponAmount}
}
return false
return { isValid: false, amount: couponAmount}
} finally {
release()
}
Expand Down
87 changes: 54 additions & 33 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SendTokenResponse,
ChainType,
EVMInstanceAndConfig,
ERC20Type
CouponValidity
} from './types'

import {
Expand Down Expand Up @@ -94,7 +94,7 @@ evmchains.forEach((chain: ChainType): void => {
})

// Adding ERC20 token contracts to their HOST evm instances
erc20tokens.forEach((token: ERC20Type, i: number): void => {
erc20tokens.forEach((token: any, i: number): void => {
if(token.HOSTID) {
token = populateConfig(token, getChainByID(evmchains, token.HOSTID))
}
Expand All @@ -112,39 +112,60 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
const erc20: string | undefined = req.body?.erc20
const coupon: string | undefined = req.body?.couponId

const evm: EVMInstanceAndConfig = evms.get(chain)!
if(evm) {
if(
couponConfig.IS_ENABLED &&
(
(erc20 && evm.instance.contracts.get(erc20)?.config.COUPON_REQUIRED) ||
(erc20 === undefined && evm.config.COUPON_REQUIRED)
)
) {
if(!coupon || !(await couponService.consumeCouponAmount(coupon, evm.config.DRIP_AMOUNT))) {
res.status(400).send({message: "Invalid or expired coupon passed!"})
return
}
}
// initialize instances
const evm = evms.get(chain)
const erc20Instance = evm?.instance?.contracts?.get(erc20 ?? "")

DEBUG && console.log(
"address:", address,
"chain:", chain,
"erc20:", erc20,
"ip:", req.headers["cf-connecting-ip"] || req.ip
)
evm.instance.sendToken(address, erc20, async (data: SendTokenResponse) => {
const { status, message, txHash } = data

// validate and consume coupon if required
if (evm.config.COUPON_REQUIRED && coupon && txHash === undefined) {
await couponService.reclaimCouponAmount(coupon, evm.config.DRIP_AMOUNT)
}
res.status(status).send({message, txHash})
})
} else {
res.status(400).send({message: "Invalid parameters passed!"})
// validate parameters
if (evm === undefined || (erc20 && erc20Instance === undefined)) {
res.status(400).send({ message: 'Invalid parameters passed!' })
return
}

// unique id for each token
const faucetConfigId = erc20Instance?.config.ID ?? evm?.config.ID

// drip amount (native or erc20 token) for this request as per config
const dripAmount = erc20Instance?.config.DRIP_AMOUNT ?? evm.config.DRIP_AMOUNT

// validate coupon
let couponValidity: CouponValidity = {isValid: false, amount: dripAmount}

if (
couponConfig.IS_ENABLED &&
(erc20Instance && erc20Instance.config.COUPON_REQUIRED) ||
(erc20Instance === undefined && evm.config.COUPON_REQUIRED)
) {
// 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 passed!"})
return
}
}

// logging requests (if enabled)
DEBUG && console.log(
"address:", address,
"chain:", chain,
"erc20:", erc20,
"ip:", req.headers["cf-connecting-ip"] || req.ip
)

// send request
evm.instance.sendToken(address, erc20, couponValidity.amount, 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)
}
res.status(status).send({message, txHash})
})
})

// GET request for fetching all the chain and token configurations
Expand Down
7 changes: 6 additions & 1 deletion types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,17 @@ export type ERC20Type = {
WINDOW_SIZE: number,
MAX_LIMIT: number
},
COUPON_REQUIRED?: boolean,
IMAGE?: string,
RECALIBRATE?: number,
RPC?: string,
CHAINID?: number,
EXPLORER?: string,
MAX_PRIORITY_FEE?: string,
MAX_FEE?: string,
COUPON_REQUIRED?: boolean,
}

export type CouponValidity = {
isValid: boolean,
amount: number,
}
19 changes: 13 additions & 6 deletions vms/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default class EVM {
async sendToken(
receiver: string,
id: string | undefined,
customAmount: number | undefined,
cb: (param: SendTokenResponse) => void
): Promise<void> {
if(this.blockFaucetDrips) {
Expand All @@ -144,19 +145,23 @@ export default class EVM {
// increasing request count before processing request
this.requestCount++

let amount: bigint = this.DRIP_AMOUNT
let amount = this.DRIP_AMOUNT
let decimals = this.DECIMALS

// If id is provided, then it is ERC20 token transfer, so update the amount
if(id) {
const contract = this.contracts.get(id)
if (contract) {
const dripAmount: number = contract.config.DRIP_AMOUNT
if(dripAmount) {
amount = calculateBaseUnit(dripAmount.toString(), contract.config.DECIMALS || 18)
}
amount = contract.dripAmount
decimals = contract.decimals
}
}

// use custom amount
if(customAmount) {
amount = calculateBaseUnit(customAmount.toString(), decimals)
}

const requestId = receiver + id + Math.random().toString()

this.processRequest({ receiver, amount, id, requestId })
Expand Down Expand Up @@ -461,7 +466,9 @@ export default class EVM {
this.contracts.set(config.ID, {
methods: (new this.web3.eth.Contract(JSON.parse(JSON.stringify(ERC20Interface)), config.CONTRACTADDRESS)).methods,
balance: BigInt(0),
config
config,
dripAmount: calculateBaseUnit(config.DRIP_AMOUNT.toString(), config.DECIMALS || 18),
decimals: config.DECIMALS || 18,
})
}

Expand Down
2 changes: 2 additions & 0 deletions vms/evmTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ export type ContractType = {
methods: any,
balance: bigint,
config: ERC20Type,
dripAmount: bigint,
decimals: number,
}

0 comments on commit 82b07ec

Please sign in to comment.