TGT Finance Audit
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.
Table of Content
Project Summary
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.
Service Scope
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
- Audit Service
-
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.
Architecture
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 contract
protocol/interfaces(folder)
: protocol interfaces
protocol/libary(folder)
: libraries for the procotols
protocol/strategies/uniswap(folder)
:
StrategySaveBaseTokenOnly.sol
StrategyWithdrawTrading.sol
protocol/worker(folder)
:
WorkerConfig.sol
UniswapWorker.sol
BankController.sol
: controller contract and help functions
ChainLinkPriceOracle.sol
: upgradable contracts for chainlink oracle
ConfigurableInterestVaultConfig.sol
: interest vault config contract
Exponential.sol
: math library
FToken.sol
: FToken contract main user interaction logic
FTokenHelper.sol
: helper contract
InterestRateModel.sol
: interest rate model contract
IPriceOracle.sol
: price oracle interface
TokenVault.sol
: vault contract
Vault.sol
: margin trading related vault contract
WNativeRelayer.sol
: relayer contract to improve security
Privileged Roles
-
FToken.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
-
WNativeReplayer.sol
owner
:
setCallerOk()
: set and remove whitelisted caller
whitelistedCallers()
:
-
UniswapWorker.sol
owner
:
setParams()
: set two kinds strategists.
setStrategyOk()
: set or remove strategists.
-
InterestRateModel.sol
-
TokenVault.sol
owner
:
- grant or revoke permissions
worker
:
Findings & Improvement Suggestions
InformationalMinorMediumMajorCritical
|
Total |
Acknowledged |
Resolved |
Critical |
0 |
0 |
0 |
Major |
3 |
3 |
3 |
Medium |
3 |
3 |
3 |
Minor |
11 |
11 |
11 |
Informational |
16 |
16 |
16 |
Major
-
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.
Medium
-
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:
else {
return BASIC_INTEREST + interestSlope1 + interestSlope2;
}
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.
Minor
-
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:
- Variable declearation
uint256 totalCash
in calcExchangeRate()
(L394) and in exchangeRateAfter()
shadows exisiting declearation.
- The return value
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:
- Add a
nonReentrant
modifier to this function and make it an external
function.
- Make this function an
internal
or private
function
Result: 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.
(address farm, uint256 poolId) = config.getFarmConfig(address(this));
if(farm != address(0)) {
}
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:
function checkAccountsIn(address account, IFToken fToken) external view returns (bool) {
return markets[IFToken(address(fToken)).underlying()].accountsIn[account];
}
Recommendation: Replace with the following code.
function checkAccountsIn(address account, IFToken fToken) external view returns (bool) {
return markets[fToken.underlying()].accountsIn[account];
}
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:
- Zero address check for
minter
and account
in BankController.mintCheck()
and BankController.borrowCheck()
- Input number range check for
_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.
mapping(address => mapping(address => bool)) isPoolCreated;
function createPool(
IERC20Upgradeable _stakingToken,
IERC20Upgradeable _rewardsToken,
address _rewardsDistributor
) public onlyOwner {
require(!isPoolCreated[_stakingToken][_rewardsToken], "TRP: POOL EXISTED!");
isPoolCreated[_stakingToken][_rewardsToken] = true;
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:
receive() external payable {
require(msg.sender == wnative, "WNativeRelayer::onlyWnative");
}
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.
Disclaimer
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.
tags:
Final Report
TGT Finance Audit
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.
Table of Content
Project Summary
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.
Service Scope
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.
Architecture
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 securityPrivileged Roles
FToken.sol
controller
:_reduceReserves()
_addReservesFresh()
component
: it can becontroller
, this contract itself or eligibleFToken.sol
contracts.transferToUser()
transferIn()
addTotalCash()
subTotalCash()
vault
: permited vaultsrepayInternalForLeverage()
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()
Findings & Improvement Suggestions
InformationalMinorMediumMajorCritical
Major
Reentrancy attack risk at
FToken.deposit()
(FToken.deposit()
: L296). MajorDescription: Possible reentrancy attack might happen at
FToken.deposit()
. It associates with sending native token back toaccount
which might have reentrancy attack inside its fallback functions.Recommendation: Add
nonReenntrant
modifier forFToken.deposit()
and remove thenonReentrant
formint()
.Result: Fixed in commit 135479ea1fd773e1446b07451dfa399e03071be2.
Change the constructor to a initialize function if it’s an updagrable contract (
WNativeRelayer.sol
,TokenVault.sol
).MajorDescription: 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
). MajorDescription: pending owner cannot claim pending ownership since
transferOwnership
function can only be called byowner
.Recommendation: Rewrite the function
claimOwnership
.Result: Fixed in commit fa6df5b1f130a5182b25eb9cbacc0f5a55232dd7.
Medium
Returns unexpected value when
utilization
is bigger or equal toMAX_USAGE_RATE
atInterestRateModel._getPureAPR()
based on comment (InterestRateModel.sol
: L75).MediumDescription: The return value should be
BASIC_INTEREST + interestSlope1 + interestSlope2
whenutilization
is bigger or equal toMAX_USAGE_RATE
.Recommendation:
Result: Fixed in commit ab2fb8ed5fe8529892ae72f318cc71c807e3f244.
OwnableUpgradeSafe
andReentrancyGuardUpgradeSafe
are not initialized (TokenVault.sol
: L147, L169). MediumDescription:
OwnableUpgradeSafe
inWhitelist
andReentrancyGuardUpgradeSafe
inTokenVault
contract are not initialized.Recommendation: Initialize
OwnableUpgradeSafe
ReentrancyGuardUpgradeSafe
inside the initialize function.Result: Fixed in commit fa6df5b1f130a5182b25eb9cbacc0f5a55232dd7.
Reentrancy attack risk at
Vault.work()
andVault.kill()
(Vault.work()
: L197,Vault.kill()
: L309). MediumDescription: Users interact with vault to perform margin trading through functions
Vault.work()
andVault.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 toVault.work()
andVault.kill()
.Result: Fixed in commit fb4b983c5bc78b9dd5055274213205a74eabf0c0.
Minor
Unused
OwnableUpgradeSafe
and librarySafeMath
in (StrategySaveBaseTokenOnly.sol
,StrategyWithdrawTrading.sol
,WorkerConfig.sol
) MinorDescription: There is no function that requires the
owner
role soOwnableUpgradeSafe
is unnecessary. LibrarySafeMath
is currently unused.Recommendation: Remove unused inheritance and libraries.
Result: Fixed in commit 43dfbb927e4f99c58fa5e5e745adc4d716407e26.
Unsued modifier. (
UniswapWorker.sol
: L87,Vault.sol
: L103) MinorDescription: modifier
onlyEOA
inUniswapWorker.sol
andinExec
inVault.sol
are unused.Recommendation: Remove the unused modifiers.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Variable declarations shadow existing declarations (
BandPriceOracle.sol
:L34, L44FToken.sol
:L394, L403) MinorDescription:
uint256 totalCash
incalcExchangeRate()
(L394) and inexchangeRateAfter()
shadows exisiting declearation.price
ofBandPriceOracle.getPrice()
and the variableprice
defined inBandPriceOracle.get()
shadow the existing valuableprice
.Recommendation: Rename those variables.
Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
The variable
address worker
inevent Work()
not indexed (Vault.sol
: L30). MinorDescription: The variable
address worker
inevent Work()
not indexed.Recommendation: Add
indexed
modifier to the variableaddress
Result: Fixed in commit 1c66a650cd01d24bf2c27c2e49e85b1419b004d2.
Unused function parameter
address _underlying
intransferToUserInternal()
(FToken.()
L157). MinorDescription: 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). minorDescription:
FToken.transferIn()
is currently only used asexternal
functions here. To enhance security, we have two alternative modification suggestions for this function.Recommendation:
nonReentrant
modifier to this function and make it anexternal
function.internal
orprivate
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). MinorDescription: 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 corrspondingnewPauser
andoldPauser
(BankController
: L37). MinorDescription: The
PauserSet()
decleration does not include corrsponding data fields (newPauser
andoldPauser
).Recommendation: Add
newPauser
andoldPauser
intoPauserSet()
decleration.Result: Fixed in commit f6193adc03ee7c4ff52ef1127de8c2b0c320b7b3.
Redundant type casting from
address
toIFToken
(BankController
: L274). MinorDescription:
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
andaccount
inBankController.mintCheck()
andBankController.borrowCheck()
_minDebtSize
inConfigurableInterestVaultConfig.setMinDebtSize()
Result: Fixed in commit c7ce45de5d308596719a376e97878a0b7ea9cf83.
Check if pool already exists when creating new pool (
TokenRewardPool.createPool()
: L86) MinorDescription: 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.
Informational
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
). InformationalDescription: 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 implementIBankController
interface (BankController
: L16). InformationalDescription: Not implementing the interface may cause the run time error if function is defined in
BankController.sol
without a declaration inIBankController
.Recommendation: Add
IBankController
in the list of implementation.Result: Fixed in commit 69d3c5a6635fdf2c3f364684e0a2cba6f456b8d4
InterestRateModel
should implement theIInterestRateModel
interface (InterestRateModel.sol
: L7). InformationalDescription: Not implementing the interface may cause the run time error if function is defined in
InterestRateModel
without a declaration inIInterestRateModel
.Recommendation: Add
IInterestRateModel
in the list of implementation.Result: Fixed in commit df588cccd2fe8b8a41dd5cc27d1780a8ac5f5001.
Redundant interface
IInterestRateModel.sol
andInterestModel.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). InformationalDescription: The
FToken
does not implement the standard IERC20 interface.FToken.transfer()
does not have bool return andFToken.approve()
does not emitApproval
event. Thus it does not follows standardsRecommendation: 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). InformationalDescription: 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). InformationalDescription: 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 toamountOutMin
.Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091. Required after balance bigger than before balance.
Remove
payable
keyword in functionexecuteWithData
(StrategySaveBaseTokenOnly.executeWithData()
: L30,IStrategy.executeWithData()
: L5). InformationalDescription: All
IStrategy
contracts we currently have only deal with ERC20 tokens. No need forpayable
keyword.Recommendation: Remove
payable
.Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Use existing
onlyAdmin()
modifier instead ofrequire(msg.sender==admin)
(BankController.sol
: L151, L172). InformationalDescription: The
_setMarketBorrowSupplyCaps()
andsetTokenConfig()
implment existing authentication login defined inonlyAdmin()
.Recommendation: Replace
require(msg.sender==admin)
withonlyAdmin()
modifier.Result: Fixed in commit a39151f24de112244b0051de0aaacd71fb4fb091.
Suggest only receive from
wnative
(WNativeRelayer.receive()
: L37). InformationalDescription: Add a require to
receive()
to require themsg.sender
iswnative
. So onlywnative
can send native tokens to this contract.Recommendation:
Result: Fixed in commit 8c9ac7d8d7101da3059df5c1e3c41182af94d710.
OPTICAL_USAGE_RATE
,MAX_USAGE_RATE
andBASIC_INTEREST
can be constant variables (InterestRateModel.sol
). InformationalDescription:
OPTICAL_USAGE_RATE
,MAX_USAGE_RATE
andBASIC_INTEREST
can be constant variables.Recommendation: Set
OPTICAL_USAGE_RATE
,MAX_USAGE_RATE
andBASIC_INTEREST
to constant variables.Result: Fixed in commit ca81ff479fd0ea435151f4115790eeda0c58b13b.
Wrong error message in
withdraw()
(TokenVault
: L212). InformationalDescription: 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)InformationalDescription: 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 foronlyEOA
modifier (Vault.sol
: L85). InformationalDescription: Currently, it only checks if an account is EOA by comparing
msg.sender
andtx.origin
. There are many ways to bypass this check (i.e. usingdelegatecall
).Recommendation: Use openzeppelin
Address
library foronlyEOA
modifier to check if an account is EOA.Result: Fixed in commit e8e8afe2f11b73b2ee4c5b765ee833a7a9672af4.
Interface
IVault.sol
is empty. InformationalDescription:
IVault
interface can be removed fromVault
inheritance sinceIVault
is empty.Recommendation: Remove
IVault.sol
.Result: Fixed in commit 14d0251eebb299edea73eeea4eb0473e2234b278.
Disclaimer
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.