Final Report
Copyright © 2022 by Verilog Solutions. All rights reserved.
February 24, 2022
by Verilog Solutions
This report presents Verilog's smart contract auditing engagement with TGT Protocol. TGT Protocol is one of the first lending protocols and margin trading platforms on the Emerald Paratime of Oasis Network.
TGT Finance is a lending protocol built on Oasis Network. TGT Finance utilized a pooled collateral/deposit model to increase capital efficiency. TGT Finance also provides native leverage trading products, which offer a streamlined user experience to traders.
Our audit is conducted on the main branch, with commit hash 1c66a650cd01d24bf2c27c2e49e85b1419b004d2.
Our engagement with TGT Protocol includes the following two services:
Pre-Audit Consulting Service
As a part of the pre-audit service, the Verilog team worked closely with the TGT development team to discuss potential vulnerability and smart contract development best practices in a timely fashion. Verilog team is very appreciative of establishing an efficient and effective communication channel with the TGT team, as new findings were often exchanged promptly and fixes were deployed quickly, during the preliminary report stage.
Audit Service
The Verilog team conducted a thorough study of the TGT code, with the TGT architecture graph and UML graph presented below in the TGT Architecture section. The list of findings, along with the severity and solution, is available under section Findings & Improvement Suggestions.
Listed below are the major smart contracts in the TGT Finance Protocol.
TokenRewardPool.sol
: for actions (supply reward, stake, withdraw, claim reward)utils(folder)
utility contracts and library contractprotocol/interfaces(folder)
: protocol interfacesprotocol/libary(folder)
: libraries for the procotolsprotocol/strategies/uniswap(folder)
:
StrategySaveBaseTokenOnly.sol
StrategyWithdrawTrading.sol
protocol/worker(folder)
:
WorkerConfig.sol
UniswapWorker.sol
BankController.sol
: controller contract and help functionsChainLinkPriceOracle.sol
: upgradable contracts for chainlink oracleConfigurableInterestVaultConfig.sol
: interest vault config contractExponential.sol
: math libraryFToken.sol
: FToken contract main user interaction logicFTokenHelper.sol
: helper contractInterestRateModel.sol
: interest rate model contractIPriceOracle.sol
: price oracle interfaceTokenVault.sol
: vault contractVault.sol
: margin trading related vault contractWNativeRelayer.sol
: relayer contract to improve securityFToken.sol
controller
:
_reduceReserves()
_addReservesFresh()
component
: it can be controller
, this contract itself or eligible FToken.sol
contracts.
transferToUser()
transferIn()
addTotalCash()
subTotalCash()
vault
: permited vaults
repayInternalForLeverage()
addReservesForLeverage()
TokenRewardPool.sol
owner
createPool()
setPoolRewardsBlockCount()
setPoolRewardsDistributor()
BandPriceOracle.sol
owner
setTokenName()
setBaseToken()
ChainLinkPriceOracle.sol
owner
setBaseToken()
setBufferDay()
setPriceFeed()
BankController.sol
multisig
proposeNewAdmin()
reduceReserves()
batchReduceReserves()
batchReduceAllReserves(address[], address payable)
batchReduceAllReserves(address payable)
admin
:
setTransferEthGasCost()
setOracle()
supportMarket()
supportVault()
_setCollateralAbility()
setCloseFactor()
setMarketIsValid()
setPauser()
pauser
:
setPaused()
setUnpaused()
WorkerConfig.sol
owner
:
setOracle()
setConfigs()
WNativeReplayer.sol
owner
:
setCallerOk()
: set and remove whitelisted callerwhitelistedCallers()
:
withdraw()
UniswapWorker.sol
owner
:
setParams()
: set two kinds strategists.setStrategyOk()
: set or remove strategists.InterestRateModel.sol
owner
setSlope1AndSlope2()
TokenVault.sol
owner
:
worker
:
withdraw()
InformationalMinorMediumMajorCritical
Total | Acknowledged | Resolved | |
---|---|---|---|
Critical | 0 | 0 | 0 |
Major | 3 | 3 | 3 |
Medium | 3 | 3 | 3 |
Minor | 11 | 11 | 11 |
Informational | 16 | 16 | 16 |
Reentrancy attack risk at FToken.deposit()
(FToken.deposit()
: L296). Major
Description: Possible reentrancy attack might happen at FToken.deposit()
. It associates with sending native token back to account
which might have reentrancy attack inside its fallback functions.
Recommendation: Add nonReenntrant
modifier for FToken.deposit()
and remove the nonReentrant
for mint()
.
Result: Fixed in commit 135479ea1fd773e1446b07451dfa399e03071be2.
Change the constructor to a initialize function if it's an updagrable contract (WNativeRelayer.sol
, TokenVault.sol
).Major
Description: Avoid using constructor or assign variables values inside constructor when writing an upgradable contract.
Recommendation: The value will not be written to proxy contract's storage if assigning variables values inside constructor.
Result: Fixed in commit 6fe1904d5dca8e5b0cadf2d5c0db22d5162c31d7.
Logical conflict. pending owner cannot claim ownership (TokenVault.sol
). Major
Description: pending owner cannot claim pending ownership since transferOwnership
function can only be called by owner
.
Recommendation: Rewrite the function claimOwnership
.
Result: Fixed in commit fa6df5b1f130a5182b25eb9cbacc0f5a55232dd7.
Returns unexpected value when utilization
is bigger or equal to MAX_USAGE_RATE
at InterestRateModel._getPureAPR()
based on comment (InterestRateModel.sol
: L75).Medium
Description: The return value should be BASIC_INTEREST + interestSlope1 + interestSlope2
when utilization
is bigger or equal to MAX_USAGE_RATE
.
Recommendation:
Result: Fixed in commit ab2fb8ed5fe8529892ae72f318cc71c807e3f244.
OwnableUpgradeSafe
andReentrancyGuardUpgradeSafe
are not initialized (TokenVault.sol
: L147, L169). Medium
Description:OwnableUpgradeSafe
in Whitelist
and ReentrancyGuardUpgradeSafe
in TokenVault
contract are not initialized.
Recommendation: Initialize OwnableUpgradeSafe
ReentrancyGuardUpgradeSafe
inside the initialize function.
Result: Fixed in commit fa6df5b1f130a5182b25eb9cbacc0f5a55232dd7.
Reentrancy attack risk at Vault.work()
and Vault.kill()
(Vault.work()
: L197, Vault.kill()
: L309). Medium
Description: Users interact with vault to perform margin trading through functions Vault.work()
and Vault.kill()
. There are many calls to external contracts and complicated variable calculations inside these two functions. nonReentrant
modifier is recommended to avoid possible reentrancy attack.
Recommendation: Add nonReentrant
modifier to Vault.work()
and Vault.kill()
.
Result: Fixed in commit fb4b983c5bc78b9dd5055274213205a74eabf0c0.
Unused OwnableUpgradeSafe
and library SafeMath
in (StrategySaveBaseTokenOnly.sol
, StrategyWithdrawTrading.sol
, WorkerConfig.sol
) Minor
Description: There is no function that requires the owner
role so OwnableUpgradeSafe
is unnecessary. Library SafeMath
is currently unused.
Recommendation: Remove unused inheritance and libraries.
Result: Fixed in commit 43dfbb927e4f99c58fa5e5e745adc4d716407e26.
Unsued modifier. (UniswapWorker.sol
: L87, Vault.sol
: L103) Minor
Description: modifier onlyEOA
in UniswapWorker.sol
and inExec
in Vault.sol
are unused.
Recommendation: Remove the unused modifiers.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Variable declarations shadow existing declarations (BandPriceOracle.sol
:L34, L44 FToken.sol
:L394, L403) Minor
Description:
uint256 totalCash
in calcExchangeRate()
(L394) and in exchangeRateAfter()
shadows exisiting declearation.price
of BandPriceOracle.getPrice()
and the variable price
defined in BandPriceOracle.get()
shadow the existing valuable price
.Recommendation: Rename those variables.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
The variable address worker
in event Work()
not indexed (Vault.sol
: L30). Minor
Description: The variable address worker
in event Work()
not indexed.
Recommendation: Add indexed
modifier to the variable address
Result: Fixed in commit 1c66a650cd01d24bf2c27c2e49e85b1419b004d2.
Unused function parameter address _underlying
in transferToUserInternal()
(FToken.()
L157). Minor
Description: Unused function parameter address _underlying
.
Recommendation: Remove or Comment out the variable name. Otherwise, put it in a placeholder expression _underlying;
Result:Fixed in commit 3a0b6664ef947ddaeb2f4d6474c18068c1cbdce3.
Scope and modifier for FToken.transferIn()
function(FToken.transferIn()
: L172, FToken.approve()
: L255). minor
Description: FToken.transferIn()
is currently only used as external
functions here. To enhance security, we have two alternative modification suggestions for this function.
Recommendation:
nonReentrant
modifier to this function and make it an external
function.internal
or private
functionResult: Fixed in commit 9fba8673ceb0c8c68300846fb2d819aa451750ee.
Function calls might fail if farm contract address is zero address (FToken.transferTokens()
: L249, FToken.deposit()
: L301, FToken.withdrawInternal()
: L507, FToken.seizeInternal()
: L886). Minor
Description: There are calls to farm contract, which is set in the contract ConfigurableInterestVaultConfig
. Calls might fail if farm contract is not set or set to zero address for current FToken contract.
Recommendation: Do Address zero check first on farm address then do interactions with it.
Result: Fixed in commit 70d0c87fff53646e50d4a34e283b84486a6298fc.
event PauserSet()
lacks corrsponding newPauser
and oldPauser
(BankController
: L37). Minor
Description: The PauserSet()
decleration does not include corrsponding data fields (newPauser
and oldPauser
).
Recommendation: Add newPauser
and oldPauser
into PauserSet()
decleration.
Result: Fixed in commit f6193adc03ee7c4ff52ef1127de8c2b0c320b7b3.
Redundant type casting from address
to IFToken
(BankController
: L274). Minor
Description:
Recommendation: Replace with the following code.
Result: Fixed in commit 14d4cc7c64cd1e2bfc1caa3ba1d9f8782ec2eb7f.
Sanitize Input Minor
Description: We suggest adding input sanitization checks when assigning function input parameters to variables.
Recommendation: Add input sanitization checks when assigning function input parameters to variables. For example:
minter
and account
in BankController.mintCheck()
and BankController.borrowCheck()
_minDebtSize
in ConfigurableInterestVaultConfig.setMinDebtSize()
Result: Fixed in commit c7ce45de5d308596719a376e97878a0b7ea9cf83.
Check if pool already exists when creating new pool (TokenRewardPool.createPool()
: L86) Minor
Description: Check if pool already exists when creating new pool.
Recommendation: Add a mapping to track all added pool. Check if pool existed in TokenRewardPool.createPool()
first.
Result: Fixed in commit 85cfad57f495e8a138ab7f92995be6e14b7a16b5.
General practice suggestions. Informational
Description: The are many functions associated with transferring users' funds following a series of other operations (i.e. FToken.repay()
). We recommend transferring in funds first then do these operations.
Recommendation: Transfer in funds first then do the rest of operations.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091. File deleted.
The IDebtToken
interface is defined but never used (IDebtToken
). Informational
Description: The IDebtToken
interface is defined but not implemented by any contracts.
Recommendation: Remove the interface.
Result: Fixed in commit ed1298dff74e9636948932aa2a58b843470d92f2. File deleted.
BankController.sol
Should implement IBankController
interface (BankController
: L16). Informational
Description: Not implementing the interface may cause the run time error if function is defined in BankController.sol
without a declaration in IBankController
.
Recommendation: Add IBankController
in the list of implementation.
Result: Fixed in commit 69d3c5a6635fdf2c3f364684e0a2cba6f456b8d4
InterestRateModel
should implement the IInterestRateModel
interface (InterestRateModel.sol
: L7). Informational
Description: Not implementing the interface may cause the run time error if function is defined in InterestRateModel
without a declaration in IInterestRateModel
.
Recommendation: Add IInterestRateModel
in the list of implementation.
Result: Fixed in commit df588cccd2fe8b8a41dd5cc27d1780a8ac5f5001.
Redundant interface IInterestRateModel.sol
and InterestModel.sol
.
Description: Two interfaces are about interest rate model.
Recommendation: Keep IInterestRateModel.sol
.
Result: Fixed in commit 43261b8c350657091507bd837d578f6e95c0aa50.
FToken.sol
contract should implement the IERC20 standards (FToken.transfer()
: L205, FToken.approve()
: L255). Informational
Description: The FToken
does not implement the standard IERC20 interface. FToken.transfer()
does not have bool return and FToken.approve()
does not emit Approval
event. Thus it does not follows standards
Recommendation: Modifies the functions to follow the standard IERC20 interface and also modifiers the IFToken.sol
as well.
Result: Fixed in commit 8c1ed9f9061216e334c88d02ca6025e379fef636.
Comments // 2. Convert farming tokens to base tokens.
is wrong (StrategySaveBaseTokenOnly
: L43). Informational
Description: The comment should be convert base tokens to farm tokens
.
Recommendation: Change comment to Convert base tokens to farm tokens
.
Result: Fixed in commit ab2fb8ed5fe8529892ae72f318cc71c807e3f244.
Add token balance differences check (StrategySaveBaseTokenOnly.executeWithData()
: L24, StrategyWithdrawTrading.executeWithData()
: L23). Informational
Description: function executeWithData
convert all base token to farm token. A balance difference check for farm token could be added to make sure farm token balance increases after swap.
Recommendation:
function StrategySaveBaseTokenOnly.executeWithData()
convert all base tokens to farm tokens. StrategyWithdrawTrading.executeWithData()
convert partial farm tokens / base tokens. Record the tokens balance before and after swap and require that balance difference is greater or equal to amountOutMin
.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091. Required after balance bigger than before balance.
Remove payable
keyword in function executeWithData
(StrategySaveBaseTokenOnly.executeWithData()
: L30, IStrategy.executeWithData()
: L5). Informational
Description: All IStrategy
contracts we currently have only deal with ERC20 tokens. No need for payable
keyword.
Recommendation: Remove payable
.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Use existing onlyAdmin()
modifier instead of require(msg.sender==admin)
(BankController.sol
: L151, L172). Informational
Description: The _setMarketBorrowSupplyCaps()
and setTokenConfig()
implment existing authentication login defined in onlyAdmin()
.
Recommendation: Replace require(msg.sender==admin)
with onlyAdmin()
modifier.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Suggest only receive from wnative
(WNativeRelayer.receive()
: L37). Informational
Description: Add a require to receive()
to require the msg.sender
is wnative
. So only wnative
can send native tokens to this contract.
Recommendation:
Result: Fixed in commit 8c9ac7d8d7101da3059df5c1e3c41182af94d710.
OPTICAL_USAGE_RATE
, MAX_USAGE_RATE
and BASIC_INTEREST
can be constant variables (InterestRateModel.sol
). Informational
Description: OPTICAL_USAGE_RATE
, MAX_USAGE_RATE
and BASIC_INTEREST
can be constant variables.
Recommendation: Set OPTICAL_USAGE_RATE
, MAX_USAGE_RATE
and BASIC_INTEREST
to constant variables.
Result: Fixed in commit ca81ff479fd0ea435151f4115790eeda0c58b13b.
Wrong error message in withdraw()
(TokenVault
: L212). Informational
Description: The error message of require(_receiver != address(0), "Illegal amount" );
unmatch.
Recommendation: Replace it with "Receiver address is zero"
.
Result: Fixed in commit c2e7ca29f122e24624e0aea8725aedd6e3833d80.
Redundant check (TokenVault.withdraw()
: L213)Informational
Description: The check for SToken balance is redundant. safeTransfer
will revert on insufficient balance.
Recommendation: Remove the redundant the require.
Result: Fixed in commit c4a6b8d55aa0b70ae3e69611e2fe0c67a2393e8b.
Use openzeppelin Address
library for onlyEOA
modifier (Vault.sol
: L85). Informational
Description: Currently, it only checks if an account is EOA by comparing msg.sender
and tx.origin
. There are many ways to bypass this check (i.e. using delegatecall
).
Recommendation: Use openzeppelin Address
library for onlyEOA
modifier to check if an account is EOA.
Result: Fixed in commit e8e8afe2f11b73b2ee4c5b765ee833a7a9672af4.
Interface IVault.sol
is empty. Informational
Description: IVault
interface can be removed from Vault
inheritance since IVault
is empty.
Recommendation: Remove IVault.sol
.
Result: Fixed in commit 14d0251eebb299edea73eeea4eb0473e2234b278.
Verilog receives compensation from one or more clients for performing the smart contract and auditing analysis contained in these reports. The report created is solely for Clients and published with their consent. As such, the scope of our audit is limited to a review of code, and only the code we note as being within the scope of our audit detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Our sole goal is to help reduce the attack vectors and the high level of variance associated with utilizing new and consistently changing technologies. Thus, Verilog in no way claims any guarantee of security or functionality of the technology we agree to analyze.
In addition, Verilog reports do not provide any indication of the technologies proprietors, business, business model, or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog has the right to distribute the Report through other means, including via Verilog publications and other distributions. Verilog makes the reports available to parties other than the Clients (i.e., “third parties”) – on its website in hopes that it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.