UsePAY

CertiKFiles named in the report

CertiK reviewed UsePay on 16 Nov 2022: 34 findings, 2 critical; 33 marked fixed, 1 open.

2 critical findings, fixed
Open the reportOpen UsePay’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings34

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical002
High102
Medium003
Low0026
CriticalIncorrect Proxy Implementation and Potentially LoFixed

The `createTicket` function calls the constructor in `TicketPack` along with a `msg.value`. `TicketPack` also has a `fallback` function that implements a proxy to a commander address. The `fallback` function (and therefore the proxy) is never called even when `msg.value` is sent to the contract because `TicketPack` has a receive function which takes in the `msg.value` instead. After this happens, tokens are locked in the `TicketPack` contract as there isn't any withdraw functionality either. The solution to this particular problem would be to remove the `receive` function.

CriticalPack Issuer Can Be Greif Attacked Out Of EscrowedFixed

In the function `calculate` there is this logic: ```solidity uint quantityCount = packInfo.total - quantity - totalUsedCount; uint qunaityValue = _percentValue(packInfo.price,packInfo.noshowValue) * quantityCount; _transfer(packInfo.tokenType,owner,qunaityValue); ``` `qunaityValue` is transferred to the owners address (withdrawing their earnings). `qunaityValue` is dependant on `quantityCount`. The problem with `quantityCount` is that totalUsedCount can be artificially increased such that `quantityCount` overflows (due to the solidity version being `>= 0.7.0`) or `quantityCount` can simply be set to 0 in version `0.8.0` and above. Here is the attack flow: For this example let's assume there is 100 available packs 1) The attacker buys 100 packs, as a result `quantity` decreases by 100. 2) The attacker requests a refund for these packs before `packInfo.times1`, this way `quantity` increases by 100 (cancelling out the previous decrease) and `totalUsedCount` increases by 100. After the attack, assuming regular users buy the tickets and eventually the time comes where the owner would like to withdraw his earnings, all the funds will be locked away from them as: ```solidity uint quantityCount = packInfo.total - quantity - totalUsedCount; ``` Since `totalUsedCount` was artificially inflated, `quantityCount` will have underflowed (in versions `^0.7.0`) making `quantityCount` so large that the transfer will always fail due to insufficient funds in the contract. Or in versions `^0.8.0`: since `totalUsedCount` is so big, the calculation will fail and the owner will never be able to withdraw is funds.

HighPotential Reentrancy AttackFixed

The Checks-Effects-Interactions pattern is not present in the code base hence re-entrancy attacks are present. Attack scenario for BNB/ETH/KLA: 1) Attack buys a pack and their `hasCount` increases, allowing them to pass the `canUse` check in `requestRefund` 2) When the attacker calls `requestRefund`, eventually BNB/ETH will be sent to the attackers address at the value of `packInfo.price` 3) In the attackers address there is a fall back function which calls `requestRefund` again. It passes the `canUse` check because the attackers `hasCount` has not yet been reduced. Therefore the attacker receives another refund. 4) The cycle continues until the contract is drained of tokens.

HighSubscription Owner Can Be Greif AttackedFixed

The owner of a subscription pack is always vulnerable to a grief attack depending on the value of `noshowLimit`. For example, an `owner` sets `packInfo.total` as 40 and `noshowLimit` as 15. Lets say 25 packs are bought by regular customers and an attacker buys 15 subscription packs. The attacker can refund his packs immediately after `packInfo.times2` is reached, setting `isLive` to 1. When time comes for the `owner` to withdraw his earnings from the other 25 packs, they are unable to do so due to the `checkLive` check on the `calculate` function, losing out on all his earned tokens.

MediumInconsistencyFixed

The contracts `BSC_SubscriptionCreator` and `BSC_TicketCreator` have essentially identical logic for the swap upon creating a pack. However in `BSC_SubscriptionCreator`, `tokenIdx` defaults to: ```solidity uint16[2] private tokenIdx = [101,550]; ``` and in `BSC_TicketCreator`, `tokenIdx` defaults to: ```solidity uint16[2] private tokenIdx = [550,101]; ``` Which are in the opposite order to each other, indicating one of the swaps being in the wrong order. The same problem extends to `couponCreator` contract but the contract does not include a `_swap` function.

MediumInconsistent Refund CalculationsFixed

In the `noShowRefund`, a percentage is taken off the refund value twice, once here: ```solidity uint refundValue = _percentValue(packInfo.price,95); ``` Which makes the refund value 95% of the price. This value is further altered here: ```solidity for( uint i = 0; i < _addrList.length; i++ ) { address _to = _addrList[i]; buyList[_to].hasCount--; (values[i], swaps[i]) = _refund(_to,refundValue,10); } ``` Where the refund value is further calculated against for fees and the total to be returned to the customer is 0.90 * 0.95 * `packInfo.price`.

MediumRedundant FunctionalityFixed

`BSC_CouponCreator` features a lot of functionality that facilitates a swap, such as `tokenIdx`, `swapCount` and functions such as `setTokenIdx` and `setSwapCount`. However a swap never takes place in the contract.

Low`checkFee` Across Contracts DiffersFixed

It is unclear why in `BSC_Commander.sol`, the `checkFee` function has a require statement that: ```solidity require( msg.value > getPrice() * (n) , "C01"); ``` While in the sister contracts in `Commander.sol` and `KLA_Commander.sol`, the `n` variable is hard coded to 5. ```solidity require( msg.value > getPrice() * n * 5 , "C01"); ``` This inconsistency is also found in the `getCountFee` function.

LowAccuracy Loss When Calculating PercentageFixed

There is some loss in accuracy when calculating percentages at the upper bound of a percentage change. For example 60 out of 100 is calculated as 59%. 500 out of 1000 is calculated as 49%, and so on.

LowCheck should also pass if `multiSign.count>numConFixed

In `launchSetAddresses()` and `launchCalculate()` a check is passed only if `multiSign.count == numConfirmationsRequired`. However, it should be allowed to pass if `multiSign.count >= numConfirmationsRequired` to make it consistent with `confirmSetAddresses()` and `confirmCalculate()`.

Show 10 more
LowConfusing ArithmeticFixed

The function `changeTotal` has this logic: ```solidity require(packInfo.total - quantity <= _count,"count too high"); if ( _count > packInfo.total ) { checkFee(_count-packInfo.total); _swap(msg.sender,msg.value); quantity = quantity + ( _count - packInfo.total ); } else { quantity = quantity - ( packInfo.total - _count ); } ``` The arithmetic: ```solidity quantity = quantity + ( _count - packInfo.total ); ``` Is exactly the same as: ```solidity quantity = quantity - ( packInfo.total - _count ); ``` The reason to use to different methods of calculation for the same result is unclear.

LowDependent on DEX Price FeedFixed

The function `getPrice` relies on `BUSD` staying stable and pegged to the dollar which may not always be the case.

LowInconsistent VisibilityFixed

In the `subscription` contract, the `PackInfo packInfo` variable is defaulted to private visibility as it has no specifier included. However in the `Coupon` and `Ticket` contracts, this particular variable is set as internal.

LowMisleading Error MessageFixed

In the function `changeTotal` there is this `require` statement: ```solidity require(packInfo.total - quantity <= _count,"count too high"); ``` The error message "count too high" is misleading because the count is supposed to be greater than `packInfo.total - quantity`. Most of the error messages in the contract are written as a key, e.g "O01","CT01". This may be understood by the developers using a reference, but for the users these error messages can not be interpreted.

LowMissing Check That `index` and `newAddress` arrayFixed

`index` is suppose to be the input array of the indexes of the multi-sig addresses that are to be changed and `newAddress` is supposed to be the input array of the new addresses. It should be checked that these two arrays have the same length.

LowMissing ChecksFixed

The `constructor` has no checks to confirm that the inputs to the `packInfo` or the owner address is valid. This may lead to unexpected results for both the buyer and the seller. ---- In the function `createTicket` in the `ticketCreator.sol` file, there is a restriction to the `packInfo.total` which is 1000. However in the `changeTotal` function in the `TicketCommander` there is no such check, meaning the owner can add as many packs as they want. Similar cases are present for `SubscriptionCommander` and `CouponCommander` (where the maximum `packInfo.total` is 3000) --- In the `checkManger` function, only indexes below 100 are checked for the managerial addresses, therefore there should be a check in `setAddresses` to ensure that _index.length is less than 100, and a check in `setAddress` to ensure that `_index` is less than 100. Furthermore there should be checks that the inputted address is not the 0 address. --- Addresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity owner = _owner; ``` - `_owner` is not zero-checked before being used. --- ```solidity owner = _owner; ``` - `_owner` is not zero-checked before being used. --- ```solidity owner = _owner; ``` - `_owner` is not zero-checked before being used. --- Throughout the contract logic, `quantity` is used to keep track of how many packs are available to buy, for example in the `_buy` function, the quantity is reduced. However in the `buy` function, there are no checks to see whether the inputted count is less than or equal to the available quantity, making `quantity` and by extension `packInfo.Total` redundant. This check in `canBuy`: ```solidity require ( quantity - count >= 0 , "B04"); ``` is subject to underflow due to solidity version `>=0.7.0`, so it does not satisfy the check. ---- The PAC eco & UsePAY ecosystem rely on PAC tokens being swapped for native tokens in events such as creating a pack. In this current implementation, when creating a ticket, coupon or subscription, there are no checks for fees, meaning that the pack creator can just insert 0 as the `msg.value`, and the ecosystem is not supported at all. This issue is also present in the BSC and KLA creation contracts. ----

LowMissing Emit EventsFixed

Functions that affect the status of sensitive variables should be able to emit events. For example: an event should be emitted when `tokenIdx` is set. The function that affects the status of sensitive variables should be able to emit events as notifications to users. --- The event `calculateEvent` has a input field for `swap`, however in the `calculate` function where the event is emitted, swap is always initialized to 0 and never changes as in the `_refund` logic, `swapValue` is calculated but this value is never returned into the `swap` variable in the `calculate` function.

LowMissing Error MessagesFixed

The **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.

LowMissing View FunctionFixed

There is a view function for all the `private` variables except for `nowSwap`.

LowModifiers Used For Other Purposes Than ConditionFixed

We do not recommend using modifiers for other purposes other than condition checks; modifiers should either revert a transaction or execute the function’s body. Breaking these rules is considered bad practice. It leads to code of poor quality, that’s hard to read and maintain and is prone to errors.

Show 10 more
LowPotential Flashloan AttackFixed

Flash loans are a way to borrow large amounts of money for a certain fee. The requirement is that the loans need to be returned within the same transaction in a block. If not, the transaction will be reverted. An attacker can use the borrowed money as the initial funds for an exploit to enlarge the profit and/or manipulate the token price in the decentralized exchanges. We find that the `getPrice` function in the `Commander.sol` contracts rely on price calculations that are based on-chain, meaning that they would be susceptible to flash-loan attacks by manipulating the price of given pairs to the attacker's benefit.

LowProper Usage of Boolean TypeFixed

The parameter `isLive` is a `uint256` and is only used as a flag that can only be `0` or `1`, the same as a boolean. Gas can be saved by just using a Boolean. In addition `isCaculated` is also only used as a flag that can only be `0` or `1`.

LowRedundant `if` and `else` StatementFixed

This `if` statement in the function `getValue` is redundant: ```Solidity if ( a == 10 && percent == 60) { return 6; ``` This is because this check is already made prior: ```Solidity if(percent == 60 ) { if ( a== 10 ) { return 6; ``` The second if statement in: ```solidity else if (block.timestamp > packInfo.times2 && block.timestamp < packInfo.times2 + 172800) { if (block.timestamp > packInfo.times2) { ``` Is redundant because the check to see whether `block.timestamp` is greater than `packInfo.times2` is already made right above it. This `else` block is never reached: ```solidity else { require(block.timestamp <= noShowTime + 15552000,"N04"); // require(block.timestamp <= noShowTime + 300,"N04"); refundValue = packInfo.price; } ``` That is because before this else block there is a check for `block.timestamp < packInfo.times2` and a check for `block.timestamp > packInfo.times2` and logically there is no alternative scenario than these two, making the final else statement redundant.

LowRedundant ABIEncoderV2Fixed

`pragma experimental ABIEncoderV2;` is still valid, but it is deprecated and has no effects since Solidity version 0.8.0. The compiler version in the contract is unlocked, so this statement is redundant at any version above and including version 0.8.0, which we recommend the project team to use. [(Reference)](https://learnblockchain.cn/docs/solidity/080-breaking-changes.html?highlight=pragma%20experimental%20abiencoderv2)

LowRedundant Modifier "payable"Fixed

The function that is declared `payable` can receive ether. The functions presented seem not to be designed for receiving ether, so it is redundant to have the `payable` modifier. The functions `setAddress` and `setAddresses` are both payable functions. However this contract does not support any use cases of having tokens sent to it. Furthermore the contract does not have any functions to withdraw, leaving any tokens sent to it lost in the contract.

LowRedundant Variable InitializationFixed

The aforementioned lines assign 0 to the `uint256` type variables. The default value for `uint256` type is 0, hence the aforementioned assignments are redundant and cost unnecessary gas.

LowSave Gas By Using Uint32Fixed

In the `Pack` contract, `total` is a `uint32` while `refundCount`, `noshowCount`, `noshowLimit` and `quantity` are all `uint256` even though these values can not exceed the value of `total`

LowStruct Packing OptimizationFixed

Struct `PackInfo` in the `Pack.sol` `subscription` contract is not following the best practice of packing struct in Solidity. Referring to [Solidity Documentation](https://docs.soliditylang.org/en/v0.6.12/internals/layout_in_storage.html?highlight=pack#layout-of-state-variables-in-storage).

LowTypoFixed

The name of the modifier `onCaculateTime` has a spelling mistake, it should be `onCalculateTime`. The word `qunaity` should be `quantity` The name of variable `checkManger` has a spelling mistake, it should be `checkManager`. ---- The file name `BSC_TikcetCreator.sol` should be `BSC_Ticketcreator.sol` ---- The comments on the aforementioned lines state: "0: pack indexed, 2: createTime, 3: PackInfo" where it should be "0: pack indexed, 1: createTime, 2: PackInfo".

LowUnnecessary ImportFixed

`BSC_CouponCommander` and `KLA_CouponCommander` both import `Pack`. ```solidity import "../../Pack/CouponPack.sol"; import "../../Pack/Pack.sol"; import "./BSC_Commander.sol"; ``` However since `CouponPack` is already imported which also inherits `Pack`, the import of `Pack` in the `CouponCommander` contracts are not needed.

Show 3 more
LowUnused or Redundant EventsFixed

The aforementioned events are not used within the contract. ```solidity event buyEvent(address indexed pack, uint256 buyNum, address buyer,uint256 count); event useEvent(address indexed pack, address user,uint256 count); event requestRefundEvent(address indexed pack, address buyer ,uint256 count); event calculateRefundEvent(address indexed pack, address[] buyers ) ; event calculateEvent(address indexed); ``` ---- The event `constructorEvent` is declared but never used. ---- `giftEvent` and `giveEvent` seem to have the exact same functionality, making one of them redundant.

LowUnused VariableFixed

The variable `refundCount` is never used within the code base.

LowVolatile AccountingFixed

According to the comments, `createNum` is the creation time of the pack. This can introduce errors if the pack creator has to input this value into the `createTicket` function which will likely lead to inaccuracy.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (29)

  • Storage/Addresses.sol
  • Creator/SubscriptionCreator.sol
  • Creator/TicketCreator.sol
  • Commander/KLAYTN/KLA_Commander.sol
  • Commander/KLAYTN/KLA_TicketCommander.sol
  • Commander/BSC/BSC_CouponCommander.sol
  • Commander/KLAYTN/KLA_CouponCommander.sol
  • Commander/Commander.sol
  • Utils/Percentage.sol
  • Creator/KLAYTN/KLA_TicketCreator.sol
  • Pack/SubscriptionPack.sol
  • Commander/TicketCommander.sol
  • Commander/KLAYTN/KLA_SubscriptionCommander.sol
  • Creator/CouponCreator.sol
  • Commander/BSC/BSC_SubscriptionCommander.sol
  • Creator/BSC/BSC_TikcetCreator.sol
  • Creator/BSC/BSC_SubscriptionCreator.sol
  • Storage/WrapAddresses.sol
  • Pack/TicketPack.sol
  • Pack/CouponPack.sol
  • Creator/BSC/BSC_CouponCreator.sol
  • Library/FullMath.sol
  • Commander/BSC/BSC_TicketCommander.sol
  • Pack/Pack.sol
  • Creator/KLAYTN/KLA_SubscriptionCreator.sol
  • Creator/KLAYTN/KLA_CouponCreator.sol
  • Commander/CouponCommander.sol
  • Commander/BSC/BSC_Commander.sol
  • Commander/SubscriptionCommander.sol

Part of UsePay’s security record

UsePay
Audits1 audit by 1 firmLast audit Nov 2022, by CertiK.
Critical findingsNo open critical findingsIn that report, 2 critical findings were reported. Each is marked fixed by the firm that reported it.
Open UsePay’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us