TangYuan-Audit

CertiK1 address on BNB Chain

CertiK reviewed TangYuanSwap (TangYuan-Audit) on 31 Jan 2024: 19 findings, 0 critical; 1 marked fixed, 18 open.

No open critical findings
Open TangYuanSwap’s security record

The original report was removed from its address (seen 16 Oct 2026).

Trustblock does not review code.

Findings19

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

SeverityOpenAcknowledgedFixed
Critical000
High401
Medium700
Low700
HighCentralized Token Holding Position RiskOpen

In the contract **MktCap**, tokens are gradually swapped to WBNB and then centralized into the `marketingAddress` EOA address(es) over time. This creates a severe centralization risk and can lead to manipulation of the tokenomics of the project. ```solidity if (amount2Marketing > 0) { uint cake; for (uint i = 0; i < marketingAddress.length; i++) { cake = amount2Marketing.mul(marketingShare[i]).div(sharetotal); IERC20(token1).transfer(marketingAddress[i], cake); } } ``` Normally a higher portion of the charged fees will be used to add liquidity to the pool to maintain the liquidity of the project. However, Tokens are constantly swapped to WBNB and transferred to the `marketingAddress`, causing price drops over time. ```solidity markting : 29 burn : 0 addL : 1 total : 30 ```

HighInitial Token DistributionOpen

All of the TangYuan tokens are sent to the contract deployer's externally-owned account (EOA) addresses. This is a centralization risk because the deployer can distribute tokens without obtaining the consensus of the community. Any compromise to these addresses may allow a hacker to steal and sell tokens on the market, resulting in severe damage to the project.

HighLack of Validation In The `recoverERC20()` FunctiOpen

Normally the function `recoverERC20(address token,uint amount)` is used to withdraw stuck tokens. However, in the contract `Token`, the owner can call `recoverERC20()` to transfer any ERC20 tokens from the Token contract including `TangYuan` tokens.

HighUsers cannot transfer tokensOpen

In the `_beforeTokenTransfer()`, when the function `getStatus()` returns true, the function `_transfer` will be reverted. ```solidity if(getStatus(from,to)){ revert InStatusError(from); } ``` In the `getStatus()` function, there are two cases to return true. ```solidity function getStatus(address from,address to) internal view returns(bool){ ... if(exFees[from]==1||exFees[from]==3) return true; if(exFees[to]==2||exFees[to]==3) return true; ... } ``` There is no context or documentation to understand what the (1,2,3) stands for. However, the owner can use the `setExFees()` function to set specific users to (1,2,3). Hence these users cannot transfer their tokens anymore.

MediumFunction Calls User-Provided Addresses with no AcOpen

Calling a user provided address is dangerous, especially in a public function with no access control restriction. An attacker could deploy a malicious contract and use the vulnerable function to trigger a call to the malicious contract, potentially stealing user funds or causing other serious damages. ```solidity function recoverERC20(address token,uint amount) public { if(token==address(0)){ (bool success,)=payable(dev).call{value:amount}(""); require(success, "transfer failed"); } else IERC20(token).transfer(dev,amount); //CertiK: token address is passed by parameter and the attacker can deploy a malicious token contract } ```

MediumIncorrect token balanceOpen

When an address does not have any tokens, `balanceOf(address)` will return 1 instead of 0.

MediumLack of reasonable boundaryOpen

In the function `setFees()`, the `buy`,`sell` and `transfer` will be set by the parameter and it can be close to or bigger than the `total`. ```solidity struct Fees{ uint buy; uint sell; uint transfer; uint total; } function setFees(Fees calldata fees_) public onlyOwner{ fees=fees_; } ``` Hence the `feeAmount` can be close to or bigger than the `amount`. ```solidity uint fee=ispair[from]?fees.buy:ispair[to]?fees.sell:fees.transfer; uint feeAmount= amount.mul(fee).div(fees.total); ```

MediumPotential flashloan attack due to reliance on a sOpen

The contract's reliance on a single Dex pair for price feeds in the `getToken0Price` and `getToken1Price` functions exposes it to flashloan attack risks. An attacker can use a flashloan as the initial funds for an exploit to enlarge the profit or manipulate the token price in decentralized exchanges.

MediumPotential Out-of-Gas ExceptionOpen

We also noticed that there are some transactions that has reverted on chain. https://bscscan.com/txs?a=0x683e9dcf085e5efcc7925858aace94d4b8882024&f=1. Most of them are because of out of gas exception. It seems that function `trigger()` has consumed too much gas.

MediumPotential Sandwich AttackOpen

In a front-running attack, an attacker identifies a pending transaction on the public blockchain and jumps ahead of it (typically by paying higher gas fees). A sandwich attack is a specialized form of front-running. In this scenario, attackers identify users making token swaps or adding liquidity without appropriate slippage limits. The attacker then profits by buying the asset before the victim's transaction and selling it immediately afterwards. The following functions are called without setting restrictions on slippage or minimum output amount, so transactions triggering these functions are vulnerable to sandwich attacks, especially when the input amount(`maxSell` in this case) is large: - `swapExactTokensForETHSupportingFeeOnTransferTokens()` - `swapExactTokensForETHSupportingFeeOnTransferTokens()` ```solidity=113 router.swapExactTokensForTokensSupportingFeeOnTransferTokens( amount0In, 0, path, address(_tokenDistributor), block.timestamp ); ``` Function `MktCap._sell` invokes `IRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens` without setting restrictions on slippage or minimum output amount. --- ```solidity=143 router.addLiquidity( token0, token1, amount0, amount1, 0, 0, dev, block.timestamp ); ``` Function `MktCap._addL` invokes `IRouter.addLiquidity` without setting restrictions on slippage or minimum output amount.

Show 8 more
MediumsetPair should not be used standaloneOpen

The function Token.setPairs() is used to set the mapping ispair, which means multiple pairs can be registered. This function can be used by the owner and can be used standalone. The function MktCap.setPair() is used to set `MktCap.token1` and `MktCap.pair`. The `MktCap.pair` is the only one marketing pair for the token. This function cannot be used by the owner to change the marketing pair standalone after the project starts. The only correct way is to call the `start()` function which will call the setPairs and setPair together. ```solidity function start(address baseToken,Fees calldata fees_) public onlyOwner{ setPairs(baseToken); setPair(baseToken); setFees(fees_); } ``` Secondly, addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. ```solidity function setPair(address token) public onlyOwner { token1 = token; _tokenDistributor = new TokenDistributor(token1); IERC20(token1).approve(address(router), uint(2 ** 256 - 1)); pair = IFactory(router.factory()).getPair(token0, token1); } ``` --- `token` is not zero-checked before being used.

LowCostly Operation Inside LoopOpen

Reading, initializing, and modifying storage variables cost more gas than operating local variables, and this gas cost can significantly increase when these operations are performed inside a loop. Reference: https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html#storage-memory-and-the-stack [internal use only: e.g., https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop] ```solidity=122 ktNum = ktNum+1; ``` --- ```solidity=124 ktNum = ktNum+1; ```

LowPotential Divide by ZeroOpen

Performing division by zero would raise an error and revert the transaction. ```solidity=161 uint toBurn = amount.mul(allot.burn).div(allot.total); ``` The expression `amount.mul(allot.burn).div(allot.total)` may divide by zero. Its divisor has has estimated interval [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=162 uint toAddL = amount.mul(allot.addL).div(allot.total).div(2); ``` The expression `amount.mul(allot.addL).div(allot.total)` may divide by zero. Its divisor has has estimated interval [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=171 IERC20(token0).totalSupply().mul(autoSell.minPart).div( autoSell.parts ) ``` The expression `IERC20(token0).totalSupply().mul(autoSell.minPart).div(autoSell.parts)` may divide by zero. Its divisor has has estimated interval [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=175 uint maxSell = IERC20(token0).totalSupply().mul(autoSell.maxPart).div( autoSell.parts ); ``` The expression `IERC20(token0).totalSupply().mul(autoSell.maxPart).div(autoSell.parts)` may divide by zero. Its divisor has has estimated interval [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=185 uint amount2AddL = amount2.mul(allot.addL).div(total2Fee).div(2); ``` The expression `amount2.mul(allot.addL).div(total2Fee)` may divide by zero. Its divisor has has estimated interval [-173688133855974293135356477513031861779904976998460846059186376011869694459902, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=191 cake = amount2Marketing.mul(marketingShare[i]).div(sharetotal); ``` The expression `amount2Marketing.mul(marketingShare[i]).div(sharetotal)` may divide by zero. Its divisor has has estimated interval [0, 231584178474632390847141970017375815706539969331281128078915168015826259279870]. --- ```solidity=79 uint feeAmount= amount.mul(fee).div(fees.total); ``` The expression `amount.mul(fee).div(fees.total)` may divide by zero. Its divisor has has estimated interval [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935]. --- ```solidity=121 _receiveD = address(MAXADD/ktNum); ``` The expression `MAXADD / ktNum` may divide by zero. Its divisor has has estimated interval [0, 1461501637330902918203684832716283019655932542979].

LowPull-Over-Push Pattern In `transferOwnership()` FOpen

The change of `_owner` by function `transferOwnership()` overrides the previously set `_owner` with the new one without guaranteeing the new `_owner` is able to actuate transactions on-chain.

LowRedundant Code ComponentsOpen

The linked statements(function _buy) do not affect the functionality of the codebase and appear to be either leftovers from older functionality.

LowThird-Party Dependency UsageOpen

The contract is serving as the underlying entity to interact with one or more third party protocols. The scope of the audit treats third party entities as black boxes and assumes their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets. In addition, upgrades of third parties can possibly create severe impacts, such as increasing fees of third parties, migrating to new LP pools, etc. ```solidity=21 IRouter router; ``` - The contract `MktCap` interacts with third party contract with `IRouter` interface via `router`.

LowUnchecked ERC-20 `transfer()`/`transferFrom()` CaOpen

The return values of the `transfer()` and `transferFrom()` calls in the smart contract are not checked. Some ERC-20 tokens' transfer functions return no values, while others return a bool value, they should be handled with care. If a function returns `false` instead of reverting upon failure, an unchecked failed transfer could be mistakenly considered successful in the contract. ```solidity=120 IERC20(token1).transferFrom(address(_tokenDistributor),address(this), IERC20(token1).balanceOf(address(_tokenDistributor))); ``` --- ```solidity=134 IERC20(token0).transferFrom(address(_tokenDistributor),address(this), IERC20(token0).balanceOf(address(_tokenDistributor))); ``` --- ```solidity=180 if (toBurn > 0) IERC20(token0).transfer(address(0xdead), toBurn); ``` --- ```solidity=192 IERC20(token1).transfer(marketingAddress[i], cake); ``` --- ```solidity=133 else IERC20(token).transfer(dev,amount); ```

LowZero Token Transfer can Cause Potential DoSOpen

The function `setMarketing` does not check the parameter values non-zero. ```solidity function setMarketing( address[] calldata list, uint[] memory share ) public onlyOwner { require(list.length > 0, "DAO:Can't be Empty"); require(list.length == share.length, "DAO:number must be the same"); uint total = 0; for (uint i = 0; i < share.length; i++) { total = total.add(share[i]); } require(total > 0, "DAO:share must greater than zero"); marketingAddress = list; marketingShare = share; sharetotal = total; } ``` Hence in the trigger function, the cake to be transferred could be zero. However, transferring zero amount tokens in a loop can end up blocking the operation. ```soldity for (uint i = 0; i < marketingAddress.length; i++) { cake = amount2Marketing.mul(marketingShare[i]).div(sharetotal); IERC20(token1).transfer(marketingAddress[i], cake); } ``` This is a bit concerning as some ERC20 implementations revert on zero value transfers (see <https://github.com/d-xo/weird-erc20#revert-on-zero-value-transfers>). If at least one of the tokens includes this behavior, then the current implementation may cause a denial of service, as a zero amount transfer in this token will block the whole action and revert the transaction.

Contracts in scope1

AddressChainSource code
0x683e9dcf085e5efcc7925858aace94d4b8882024Chain: BNB ChainSource code: Verified source

Part of TangYuanSwap’s security record

TangYuanSwap
Audits1 audit by 1 firmLast audit Jan 2024, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open TangYuanSwap’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