SmartDeFi

Smart Contract Audit Report

SmartDeFi Audit Report

Executive Summary

This report presents the outcomes of our collaborative engagement with the FEG team, focusing on the comprehensive evaluation of the SmartDeFi platform.

Our team conducted an initial security assessment from January 5th to February 9th, 2024. On May 29th, this report was amended to reflect changes made to the codebase from commit 7215d4a to commit 45ad88a. These updates presented resolutions to the findings we had identified during our initial review. We previously reviewed the project team's UNCLP contract here.

SmartDeFi is a new platform which allows users to create their own tokens with liquidity fundraising and lending functionality.


Audit Scope


Audit Findings

All findings have been resolved, though some centralized aspects are present.

Finding #1

SDLGE

HighResolved

Finding #1 - SDLGE
HighResolved

Description: Any address can abort the LGE at any time before it has reached its soft cap.

Risk/Impact: Any SD Token's LGE can be aborted instantly, or after users have deposited to prevent fundraising and deny LGE functionality.

Recommendation: The project team should disallow LGE abortions before its end time is reached.

Resolution: The team has implemented the above recommendation.

Finding #2

BackingLogic

HighResolved

Finding #2 - BackingLogic
HighResolved

Description: The loanAll() function does not properly erase a user's debt from their existing expired loan. Only the first backing token's debt is erased.

for(i = 0; i < l; i++) {
	if (loansToken[user] > 0 && lendData[user] <= timeNow1) {
		loansBaseAll[user][backingAssets[i]] = 0;
	}
	how = loansToken[user];
	if(how > 0) {
		collateral -= how;
		loansToken[user] = 0; 
		lendData[user] = 0;
		SafeTransfer._pushUnderlying(IERC20(mainToken), BURN, how);
	}
}

Risk/Impact: Users will carry over debt from an expired loan when opening a new one. This debt is added to their new loan, requiring them to owe more on it than intended.

Recommendation: The logic in the for loop above should be updated to ensure that all backing token debt is erased when an expired loan is deleted.

Resolution: The team has implemented the above recommendation.

Finding #3

BackingLogic

HighResolved

Finding #3 - BackingLogic
HighResolved

Description: A user's regularLoan status does not change upon opening a new multi-token loan. If a user's single token loan expires and is replaced by a multi-token loan, they will still be marked as holding a single token loan.

Risk/Impact: Users replacing expired single token loans with multi-token loans will be prevented from repaying their loan by the current repayment logic.

Recommendation: A user's regularLoan status should be set to false when a loan is erased in the loanAll() function.

Resolution: The team has implemented the above recommendation.

Finding #4

BackingLogic

HighResolved

Finding #4 - BackingLogic
HighResolved

Description: A user's single token debt is not erased when replaced by a multi-token loan.

Risk/Impact: Certain scenarios could result in a user owing more than intended on a loan. For example, if a user takes a single token loan, replaces it with a multi-token loan, then replaces it once again with a single token loan, they will owe the sum of their current loan's debt and their first loan's debt.

Recommendation: A user's loanBase amount should be set to zero upon replacement by a multi-token loan.

Resolution: The team has implemented the above recommendation.

Finding #5

SDDeployer & SDLGE

HighResolved

Finding #5 - SDDeployer & SDLGE
HighResolved

Description: In an effort to mitigate front running risk, the platform has employed a "tick" system to compare the 4 most recent liquidity pool reserve values. The SDDeployer's FEG:WETH and Backing:WETH ticks are set upon any deposit into an LGE using them, making them susceptible to tampering.

Risk/Impact: A malicious user can manipulate ticks by first executing a large FEG or Backing token buy to significantly change reserve values. The user can then execute four negligible LGE deposits to create 4 ticks that store the new reserve values. If the user can then trigger the LGE to end, the front running check will pass as the 4 ticks that are compared will all be similar. This allows them to successfully front run the LGE's WETH→FEG swap or WETH→Backing swaps executed during its end.

Recommendation: The project team should use a price oracle instead of the current tick logic to calculate expected amounts to receive from swaps. To facilitate this, a backing token whitelist with associated oracle addresses could be introduced. The project team could alternatively allow SD token creators to provide their own oracle, but this would require additional trust in the creators. A third solution would be to only allow the SD token owner to end their LGE, and add minimum Backing token and FEG amount parameters to the endLGE() function.

Resolution: Minimum FEG and Backing amounts to receive on swaps are now specified upon ending an LGE. Additionally, only the LGE owner or a Super Admin of the Reader contract are now permitted to end the LGE, so users should ensure trust in the project teams before contributing.

Finding #6

BackingLogic

HighResolved

Finding #6 - SDT & BackingLogic
HighResolved

Description: The SD contract’s setBacking() function can only be called by the associated BackingLogic address, but the BackingLogic contract only calls SDT.setBackingAddress(), which does not exist.

Risk/Impact: The BackingLogic contract's updateBacking() function will always fail, disabling its functionality.

Recommendation: The project team should ensure that the correct function name is called when attempting to update the SD token's backing asset.

Resolution: The team has implemented the above recommendation.

Finding #7

SDT

HighResolved

Finding #7 - SDT
HighResolved

Description: The SD contract’s frontRun() function contains the following line which intends to multiply the user’s latest balance by the specified range to use for comparisons, but instead multiplies the length of the user’s balance array by the range:

uint256 a = (balance[who].length - 1) * range;

Risk/Impact: Front running checks will likely fail due to the comparison of the user's previous balances to a multiple of their balance history length.

Recommendation: The above line should be updated to the following:

uint256 a = (balance[who][balance[who].length - 1]) * range;

Resolution: The team has implemented the above recommendation.

Finding #8

SDDeployer & SDLGE

HighResolved

Finding #8 - SDDeployer & SDLGE
HighResolved

Description: The frontRun() method will now only function properly if an SD token is passed into it due to the following line.

who = tokenBackingPair[who];
The FEG address does not have a stored tokenBackingPair value in this contract, so "who" will be set to the zero address.

Risk/Impact: Front running checks will not be executed on the FEG pool when swapping for FEG in the LGE contract.

Recommendation: The "who" address should not be updated to the tokenBackingPair when called on the FEG address.

Resolution: The Reader contract is now used to fetch the correct address to check ticks on. The team has stated that calls to this contract will now function correctly when FEG front running is checked. The Reader contract was not included in the scope of this audit.

Finding #9

SDLGE & SDDeployer

HighResolved

Finding #9 - SDLGE & SDDeployer
HighResolved

Description: If significant trading occurs through either the FEG:WETH or Backing:WETH near or after the LGE's end time, the reserves in the pool may vary significantly from the most recently recorded "ticks". As ticks track reserve values to protect against front running, out of date ticks could produce false front running flags.

Risk/Impact: Failed front running checks on swaps executed during an LGE's end could prevent the LGE from ending, even if the transaction is not being front run. As users are not permitted to emergency withdraw if an LGE's end time has passed, user deposited funds would be trapped in the contract.

Recommendation: The project team should implement oracles using the recommendation provided in Finding #2. The project team can also consider only allowing the SD token owner to end their LGE and add minimum Backing token and FEG amount parameters to the endLGE() function.

Resolution: Only the SD owner or a Super Admin of the Reader contract can end the LGE, and must specify minimum Backing and FEG amounts to receive from swaps. As a result, the project team can loosen front run limits if necessary to allow for an LGE to end. Users can also now withdraw from a sold out LGE if it has not been ended after a period has passed from its sell out time.

Finding #10

SDLGE

HighResolved

Finding #10 - SDLGE
HighResolved

Description: SD Tokens can be transferred to the SD:FEG pair before an LGE is closed. If liquidity is added before an LGE is closed, the LGE's raised funds may not be added to liquidity at the intended rate, resulting in the contract receiving less LP tokens than intended.

Risk/Impact: The owner or a user holding SD tokens could add liquidity at a ratio much different than the rate defined in the LGE in exchange for liquidity tokens. All raised funds will be added to liquidity when the LGE is ended, but the LGE will receive less LP ownership than they should due to the rate mismatch. The original liquidity provider's LP tokens would then be worth more and could be redeemed for a profit at the loss of the LGE providers.

Recommendation: The project team should prohibit SD token transfers to the SD:FEG pair before the LGE has ended.

Resolution: SD Tokens can now only be transferred to the SD:FEG pair by the LGE address.

Finding #11

BackingLogic

HighResolved

Finding #11 - BackingLogic
HighResolved

Description: The following line present in both the convertBacking() and convertLiquidity() functions incorrectly calls the SD Token's frontRun() method to compare the previous SD balances of the SD Token itself instead of comparing previous SD balances of the SD:WETH Pair.

require(!sdep(mainToken).frontRun(mainToken, range), "Front Run");

Risk/Impact: These functions may incorrectly fail if the owner uses the SD token contract's token locking functionality, preventing taxed token transfers from occurring. It also may allow a swap to occur when the SD:WETH liquidity pool's reserves have been manipulated.

Recommendation: The project team should update the frontRun() function call to the following:

require(!sdep(mainToken).frontRun(uniswapV2Pair(), range), "Front Run");

Resolution: The correct frontrun() calls are now executed, and the tick mechanism has improved. The project team must still ensure that proper settings are used when calling frontRun() to mitigate the risk of manipulation. SD Token owners must also ensure to maintain low backing and liquidity thresholds to mitigate the risk of front running on these swaps.

Finding #12

SDT

HighResolved

Finding #12 - SDT
HighResolved

Description: Exchanges are not exempt from reflection rewards. As a result, Pair addresses will earn significant rewards from holding a large portion of the total supply over time.

Risk/Impact: Rewards earned by the Pair address are not added as reserves unless the sync() function is called, so any user can potentially withdraw reflection rewards from the Pair at any time.

Recommendation: The project team should add an owner ability to exclude addresses from rewards.

Update: The project team has implemented the above recommendation.

Finding #13

SDLGE

HighResolved

Finding #13 - SDLGE
HighResolved

Description: When entering an LGE with a user-specified token instead of WETH, the LGE's address is used as the token address parameter instead of the intended token.

SafeTransfer.safeTransferFrom(IERC20(address(this)), msg.sender, address(this), amount);

Risk/Impact: Users will not be permitted to enter LGEs with tokens despite intended functionality.

Recommendation: The above code in the enterLGEWithToken() function should be updated to the following:

SafeTransfer.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);

Resolution: The team has implemented the above recommendation.

Finding #14

SDDeployer, SDT, SDLGECreator, SDLGE, & BackingLogic

HighResolved

Finding #14 - SDDeployer, SDT, SDLGECreator, SDLGE, & BackingLogic
HighResolved

Description: Various fields and multipliers throughout the platform are currently set shorter than intended, likely for testing purposes. Examples of this include values in the SDLGE's lockLP(), claimShare(), checkShare(), setVesting(), editLGE(), and setInitial() functions, as well as many other values throughout the platform.

Risk/Impact: Functionality throughout the platform will not work as intended.

Recommendation: The project team should update these values to their intended amounts.

Resolution: The team has implemented the above recommendation.

Finding #15

SDDeployer

HighAcknowledged

Finding #15 - SDDeployer
HighAcknowledged

Description: KYC and ticket details are intended to be sensitive information and are access-restricted; however, any variables present on the blockchain are inherently public.

Risk/Impact: Sensitive information, including submitted KYC information, can be accessed by anyone despite intended functionality.

Recommendation: Any sensitive information should be stored off-chain.

Resolution: The team stated that sensitive information is not intended to be directly submitted to the blockchain. Users should ensure that any submitted links are access-restricted.

Finding #16

SDDeployer

HighResolved

Finding #16 - SDDeployer
HighResolved

Description: When submitting KYC in the SDDeployer.submitKYC() function, heldDonation is only incremented by the minimum ticket cost, but is decremented by the entire donated amount when confirmed or recovered. This can lead to underflow if the donated amount is larger than the minimum ticket cost.

Risk/Impact: Ticket and KYC functionality could break for an SD token if a user chooses to donate more than the minimum price for KYC verification.

Recommendation: The heldDonation variable should be incremented by the entire donation amount in the submitKYC() function.

Resolution: Donation amounts for tickets and KYC now must equal exactly the minimum price, preventing underflow.

Finding #17

SDDeployer

HighResolved

Finding #17 - SDDeployer
HighResolved

Description: The following ternary operators in the recoverDonation() and confirmKYC() functions currently execute the inverse of their intended functionality. The heldDonation variable is reset to zero if the recovered or confirmed value is less than the current total heldDonation, and the recovered or confirmed value is subtracted from heldDonation if it is greater.

heldDonation[sd] = d > heldDonation[sd] ? heldDonation[sd] - d : 0;
heldDonation[sd] = don > heldDonation[sd] ? heldDonation[sd] - don : 0;

Risk/Impact: The heldDonation variable will be incorrectly reset and will underflow in many cases, breaking ticket and KYC functionality.

Recommendation: The expressions updating the heldDonation variable should be swapped in both of the above lines.

Resolution: The team has implemented the above recommendation.

Finding #18

SDT & BackingLogic

MediumResolved

Finding #18 - SDT & BackingLogic
MediumResolved

Description: The SD Token's front running mechanism is also susceptible to manipulation if taxes are low enough. A malicious user can set their accepted slippage to the maximum of 30%, make multiple large trades to impact SD prices, then several small trades to level out recent tick values.

Risk/Impact: Swaps executed by the BackingLogic contract may not be protected from front running if the SDToken's backing threshold or liquidity threshold is large enough. Users may also be susceptible to front running and could receive less tokens than intended from a buy or sell if they do not provide a maximum slippage to the Router contract.

Recommendation: SD Token owners should ensure to set a low backing and liquidity threshold to mitigate front running risk. Users should exercise caution and avoid relying on the token's built in front running protection by specifying slippage when trading.

Resolution: Additional safety measures have been added to the tick mechanism; however, token owners and users should still follow the recommendation mentioned above. Users should also note that front running will not be checked until the user's specified number of ticks have been set.

Finding #19

SDLGE

MediumResolved

Finding #19 - SDLGE
MediumResolved

Description: The LGE contract does not accurately track the total number of whitelisted users that have deposited. When a whitelisted user deposits into an LGE with a previous deposit amount of zero, the whiteListEntered counter is incremented to count the number of unique whitelisted contributors. If a single user deposits an amount of zero twice, the counter will be incremented twice since their deposited balance has not changed.

Risk/Impact: Once the whiteListEntered counter reaches the contract's whitelist total, the LGE is automatically opened to the public. A malicious user can make repeated deposits to artificially increase the tracked number of depositors and open the LGE to the public early, denying other whitelisted users the opportunity to deposit before the public.

Recommendation: Users should not be permitted to deposit an amount of zero.

Resolution: The team has implemented the above recommendation.

Finding #20

SDLGE

MediumResolved

Finding #20 - SDLGE
MediumResolved

Description: When the owner withdraws SD tokens from the LGE through the saveTokens() function, the supplyForLP is set to 0 but the hard and soft fundraising caps remain the same.

if(token == SD) {
	if(started) {
		require(aborted && raised == 0, "rax");
	}
	a = a - totalTokensVested;
	pre.supplyForLP = 0;
}

Risk/Impact: If SD tokens are withdrawn before the LGE's start time, the LGE will function normally and accept contributions despite having no SD tokens to pair with for liquidity. This may result in WETH and FEG transfers to their respective SD liquidity pools without LP token minting, allowing any user to skim the pools to drain the transferred funds.

Recommendation: The hard and soft caps should be updated accordingly with the new token supply.

Resolution: The team has implemented the above recommendation.

Finding #21

SDLGE

MediumResolved

Finding #21 - SDLGE
MediumResolved

Description: A new LGE cannot be created for an SD token if an existing aborted LGE's funds have not been reclaimed by all users.

Risk/Impact: A malicious or inactive contributor can prevent the creation of a replacement LGE by not reclaiming their contributed funds.

Recommendation: The project team should consider adding an ownership function to reclaim deposits and send them back to their original addresses to enable new LGE creation.

Resolution: A new LGE can be created without requiring reclaimment of all funds. Users can still reclaim their deposit from the old LGE at any time.

Finding #22

SDLGECreator

MediumResolved

Finding #22 - SDLGECreator
MediumResolved

Description: When creating an LGE for an SD token, liquidity cannot already exist in the SD:FEG liquidity pool to ensure that liquidity is added correctly. The SDLGECreator.createLGE() function currently only executes this check if the pool does not exist yet.

if(fp == address(0)) {
	require(IERC20(token).balanceOf(fp) == 0, "invalid bal");
	fp = Swap(Swap(UNISWAP_V2_ROUTER()).factory()).createPair(token, FEG());
}

Risk/Impact: Leftover SD tokens or FEG may remain in the liquidity pool once liquidity is added. The starting SD token price may also not match the intended price specified during the LGE's creation.

Recommendation: This balance check should instead be called when a SD:FEG liquidity pool already exists.

Resolution: The team has resolved this issue.

Finding #23

BackingLogic

InformationalResolved

Finding #23 - BackingLogic
InformationalResolved

Description: The loanAll() function intends to only allow multi-token loans if more than one backing token exists in the contract, but can still be called if only one backing token exists.

require(l > 0, "only 1 backing");

Recommendation: The above require statement should be updated to the following:

require(l > 1, "only 1 backing");

Resolution: The team has implemented the above recommendation.

Finding #24

SDLGE

InformationalAcknowledged

Finding #24 - SDLGE
InformationalAcknowledged

Description: Users can be added to an LGE's "entered" list multiple times by depositing an amount of zero. In addition, if a user emergency exits, they are not removed from the entered list. This allows them another method of adding themselves to the entered list multiple times.

Risk/Impact: As this list serves no purpose within this platform, there is no identifiable impact from potential list inaccuracies.

Recommendation: The project team should consider removing this list or updating its adding and removal logic.

Resolution: The team has acknowledged this issue and stated that this is for UI use only. Users can no longer deposit an amount of zero, but will still remain on the entered list if they emergency exit.

Finding #25

SDT

InformationalResolved

Finding #25 - SDT
InformationalResolved

Description: A user's active status, ID, and start time all remain the same after selling their entire SD token balance, but the number of token holders is decremented. Since user IDs are assigned based on the number of token holders, any new user is assigned the same ID as the most recent user.

Risk/Impact: As these fields serve no purpose within this platform, there is no identifiable impact from this Finding.

Recommendation: The team should use a separate counter variable to provide users with unique IDs. The team can also consider restructuring the logic used to track user data to reflect their intentions.

Resolution: The number of token holders is no longer decremented upon selling their entire token balance, so each ID will now be unique.

Finding #26

SDLGE

InformationalResolved

Finding #26 - SDLGE
InformationalResolved

Description: The following nested conditional statement in the emergencyExit() function is redundant, as the buyer is already confirmed to be included in the whitelist.

if(buyer[msg.sender].whiteListed) {
	whiteListEntered = buyer[msg.sender].whiteListed ? whiteListEntered - 1 : whiteListEntered;
}

Recommendation: The whitelistEntered variable assignment can be updated to the following:

if(buyer[msg.sender].whiteListed) {
	whiteListEntered -= 1;
}

Resolution: The team has implemented the above recommendation.

Finding #27

SDLGE

InformationalResolved

Finding #27 - SDLGE
InformationalResolved

Description: The following nested conditional statement in the editWhitelist() function is redundant, as the buyer is already confirmed to be included in the whitelist.

if(!buyer[user].whiteListed) { // if not already whitelisted require _live = true and LGE not started
	require(_live);
	require(!started);
	pre.whiteListEnabled = true;
	if(!buyer[user].whiteListed) {
		whitelist.push(user);
	}
}

Recommendation: The above code can be updated to the following:

if(!buyer[user].whiteListed) { // if not already whitelisted require _live = true and LGE not started
	require(_live);
	require(!started);
	pre.whiteListEnabled = true;
	whitelist.push(user);
}

Resolution: The team has implemented the above recommendation.

Finding #28

BackingLogic

InformationalResolved

Finding #28 - BackingLogic
InformationalResolved

Description: The oneTokentoBacking() function will provide an incorrect ratio during an active flashloan.

Recommendation: This function currently can only be used for viewing purposes while a flashloan is active, but platform functionality should not rely on this function for any state-impacting decisions in future developments.

Resolution: The oneTokentoBacking() function can no longer be called during an active flash loan.

Finding #29

SDT

InformationalResolved

Finding #29 - SDT
InformationalResolved

Description: The only logic inside the _getWorth() function is a _getTValues() call.

function _getWorth(uint256 tAmount, address recipient, uint256 ref) private view returns (uint256 tTransferAmount, uint256 tShare) {
	(tTransferAmount, tShare) = _getTValues(tAmount, recipient, ref);
	return (tTransferAmount, tShare);
}

Recommendation: These functions can be combined by moving the _getTValues() logic into the _getWorth() function.

Resolution: The team has implemented the above recommendation.

Finding #30

SDT

InformationalResolved

Finding #30 - SDT
InformationalResolved

Description: The “to” parameter is not necessary in the LGE_Deployer’s admin() function since it is required to be the FEG address.

function admin(address to, bytes memory data) external {
	require(Reader(DATA_READ()).superAdmin(msg.sender) && to != address(this) && to == FEG());
	(bool success, ) = to.call{value: 0}(data);
	require(success, "tx failed");
}

Recommendation: This parameter can be removed and replaced with the FEG() address inside of the function.

Resolution: The oneTokentoBacking() function can no longer be called during an active flash loan.

Finding #31

BackingLogic

InformationalResolved

Finding #31 - BackingLogic
InformationalResolved

Description: The following code is present in the loanAll() function:

for(i = 0; i < l; i++) {
	require(timeNow1 >= lendData[user], "Settle existing");
}

Recommendation: This require statement only needs to be executed once.

Resolution: This require statement is now only executed once.


Contracts Overview

  • The contracts utilize ReentrancyGuard to prevent against reentrancy attacks in applicable functions.
  • As the contracts are implemented with Solidity v0.8.0, they are safe from any possible overflows/underflows.
  • As the Reader, StakeDeployer, FeeConverter, Dataport, and FEG contracts were not included in the scope of this audit, we are unable to provide an assessment with regards to their security or functionality.
  • Participants should ensure trust in SD Token owners as they have significant control over their own ecosystems.
SDDeployer Contract:
  • This contract allows users to create custom SmartDefi tokens.
  • Upon creation, the user specifies a name, symbol, supply, fee amounts and settings, router address for liquidity, backing token, and whether to enable an LGE.
  • Once the SD Token is created, a SD Token:WETH pair is created using the provided router's associated factory contract.
  • A liquidity pool consisting of WETH and the backing token must exist unless the WETH is used as the backing token.
  • A token's symbol cannot already be in use.
  • The sum of all token fees cannot exceed 50%.
  • Only one SD Token can be created per address.
  • This contract is also used by the platform to protect against front running through the use of "ticks", which track reserve balances.
  • Any time a new "tick" is set for a specified SD token, the FEG Pair's latest FEG balance is added to the FEG token's list of ticks.
  • The associated backing token:WETH pair's latest WETH balance is also added to the pair's list of ticks if applicable.
  • Once at least 4 ticks have been recorded for a backing token pair or the FEG token, the LGE and BackingLogic contracts can use this contract to determine if a transaction has been front run within a provided percentage rate.
  • To determine this, the average of a specified number of recently recorded ticks for the FEG token or Backing Pair are averaged and compared to the latest reserve. If the difference in these values exceeds the provided percentage rate, the transaction is considered to be front ran.
  • A user can update their own maximum permitted slippage for any registered SD Token to up 30% at any time.
  • An SD Token's owner can create a "news article" for the token, update an existing article, and update its various external-facing data at any time. These fields are used to provide information to users and do not impact token functionality.
  • Users can "submit a ticket" towards any SD token by providing at the token's ticket ETH cost.
  • A Supporter can reply to the ticket, which transfers the ETH paid to the replier and closes the ticket. The ticket's creator can also reply to their ticket. This does not return ETH to them but will reopen the ticket if it was previously closed.
  • Any user can submit KYC for an SD token at any time at the ticket cost.
  • KYC can then be confirmed by a Supporter address for the SD Token; the Supporter is transferred the deposited donation upon confirming.
  • If 3 days pass from the time of a ticket submission, the submitter can reclaim the deposited ETH for both the ticket and KYC.
  • Users can "give karma" by providing at least the SD token's minimum karma donation in ETH, specifying to add or subtract 1 karma point from the SD token. An SD token's karma cannot fall below 0.
  • An address must hold the SD token to give karma, and can only give karma once per day for a specified token.
  • Karma donations are sent to the SD token's karma donation and comment donation addresses, respectively.
  • An Admin or Super Admin of the Reader contract can disable SD Token creation at any time.
  • An SD Token's Router and Whitelisted users are permitted to bypass reentrancy restrictions.
  • An SD owner can add or remove an address as a Supporter at any time.
  • An SD owner can update their ticket cost and minimum karma donations to any amount greater than 0.1 ETH at any time.
  • An SD owner can update their karma donation addresses at any time.
  • An Admin or Setter of the associated Reader contract can manually add or remove the availability of an SD Token symbol or information at any time.
  • A Super Admin of the Reader contract can update the SDLogicDeployer address used to deploy, initialize, and determine the owner of SD tokens at any time.
  • A Super Admin of the Reader contract can update the Reader address at any time.
  • SD token buyers and LGE contributors should exercise caution and ensure a reliable Router address has been provided by the SD token owner.
SDT Contract:
  • This contract represents the SmartDeFi token, which can be created by users using the SDDeployer contract with the custom settings listed above.
  • If the SD Token has been set as a KYC-only token within the Reader contract, only users with KYC are permitted to receive token transfers.
  • When a transfer occurs, a backing fee, burn fee, liquidity fee, growth fee, staking fee, protocol fee, and reflection fee are all taken if they have been set unless the sender or recipient is exempt.
  • Transfers involving an address included in the tax exempt list or the Reader contract's whitelist are exempt from fees.
  • The burn fee is transferred to the 0x..dead address, growth fee to the SD Fee Recipient address, protocol fee to the Reader contract's Protocol address, liquidity and backing fee to the BackingLogic address, and staking fee to the Staking contract if one has been set.
  • On sells, accumulated backing and liquidity fees are converted into backing tokens and liquidity through the associated BackingLogic contract if they have exceeded the liquidity and backing thresholds.
  • If a reflection fee exists, it is distributed to all token holders.
  • Each of these fees have a separate percentage based on whether the transaction is a buy or a sell.
  • If fees are enabled for all transactions, buy fees are used for any non-sell transfers.
  • If buying or selling, the Pair's current balance is compared to the average of its second, third, and fourth most recent ticks. If the latest balance has changed by more than the user's accepted slippage percentage, the transaction will fail as a front running protection measure.
  • A user can update their permitted slippage to up to 30% at any time.
  • Buys and sells can not be executed while the token's LGE is active.
  • Token transfers cannot exceed the contract's maximum transaction limit. Transfers including a "tax free" sender or recipient are exempt from the maximum transaction limit.
  • Transfers and approvals cannot be executed while tokens are paused in the Reader contract unless the sender or recipient is the backing address or a "Super Admin" in the Reader contract, or if the recipient is the burn address.
  • The BackingLogic address can still burn tokens while the contract is paused in the Reader contract.
  • The owner can deposit and lock LP or any other tokens in this contract for any number of days at any time. The owner can also extend an existing lock at any time.
  • The owner can withdraw any tokens from the contract at any time as long as there is no active token lock.
  • This contract includes an anti-front running mechanism which compares the token's liquidity pool balance against the average of the pool's previous SD token balances, which are tracked as ticks.
  • Users should still exercise caution and specify minimum amounts to receive from trading, since this will be skipped if there is not enough tick data to be averaged. In addition, this mechanism can still potentially be exploited on certain settings if fees are low enough.
  • A user can update their permitted slippage to up to 30%.
  • A user can update the number of ticks averaged for front running checks to up to 99.
  • The owner or Reader's LGEDeployer address can add or remove a contract from the exchange list, which is used to identify buys and sells.
  • The owner may suggest new fee settings and values to up to 50%. Once the contract's time delay has passed from the time of suggestion, any address can trigger these fees to replace the previous settings.
  • An Admin of the Reader contract may update fees instantly.
  • The owner can update the maximum transaction amount to any value greater than .001% of the total supply at any time.
  • The owner, BackingLogicDeployer, StakeDeployer, or an Admin of the Reader contract can add or remove a contract from the tax exempt list.
  • The owner can update the SD Fee Recipient address.
  • The owner can update the backing or liquidity threshold to any amount greater than 1000 wei each.
  • The owner can add or remove a user from reflection rewards at any time.
  • The owner can transfer ownership to any address.
  • The Reader's "Stake Deployer" address can update the staking fee recipient address at any time.
  • The Reader contract's LGE Deployer contract can set an SD Token's LGE if an LGE is not already live.
  • The LGE must be disabled in order for the owner to propose or set new fees, update the backing threshold, update the liquidity threshold, add or remove an exchange, or transfer ownership.
  • A Reader Super Admin can update the Reader address at any time.
  • The owner can add or remove an address from the bypass list at any time.
  • The owner can add or remove an address from the whitelist at any time. Whitelisted users have the ability to bypass reentrancy restrictions.
  • Users cannot purchase tokens from the associated Pair address while the LGE is enabled.
  • An address cannot send or receive tokens more than a total of 4 times within a single block.
  • SD Token owners should ensure that both the liquidity and backing thresholds are set to low values to mitigate front running risk.
SDLGECreator Contract:
  • This contract allows users to create Liquidity Generation Events for their SmartDefi tokens.
  • When creating, the user specifies their SD token along with various settings for the LGE, including its duration, number of tokens allocated towards LP, hard cap, vesting delay, number of vesting rounds, start time, max wallet amount, token asset backing percentage, dev share, token asset backing share, and FEG share.
  • An LGE can also be created on behalf of a user by an address registered as a Protocol in the associated Reader contract.
  • The following restrictions apply when creating an LGE:
    • The vesting delay must be between 7 and 90 days.
    • The number of vesting rounds must be between 5 and 20.
    • The start time must be at least 15 minutes after the time of creation.
    • At least 10% of the SD token supply must be allocated towards liquidity.
    • A maximum of 50% of raised funds can be used for token asset backing.
    • Between 30 and 100 percent of remaining raised funds after asset backing has been taken must be allocated towards the SD:FEG pair.
    • A maximum of 50% of resulting LP tokens can be taken as a dev fee.
  • An SD Token:FEG Uniswap Pair is created for the SD Token if one does not already exist for it.
  • The LGEr address is then used to create an LGE with the SD token's backing asset; the specified token supply for liquidity is subsequently transferred from the creator to the LGE.
  • The newly created LGE is also added to the Reader contract's whitelist and set as the SD token's LGE within the SD token contract.
  • A token's LGE can be overwritten through this contract only if its existing LGE was aborted and any raised funds have been reclaimed by contributors.
  • An Admin or Setter of the associated Dataport contract can pause the contract or disable a token at any time.
  • A Super Admin of the Reader contract can update permitted LGE slippage to any value between 3 and 30%.
  • A Super Admin of the Reader contract can update the number of ticks averaged for LGE front running checks to up to 99.
  • A Super Admin of the Reader contract can update the maximum allowed number of ticks set in a block before an LGE swap is executed to any amount at any time.
  • A Super Admin of the Reader contract can update the fee to up to 500 at any time.
  • A Super Admin of the Reader contract can update the "LGEr" address used to create LGE contracts at any time.
  • A token LGE can trigger its token to end its LGE at any time through this contract, which also sets the token's exchange.
  • A token LGE can trigger the associated Reader contract to turn off the LGE's whitelist at any time.
SDLGE Contract:
  • This contract is used to facilitate an SD token's Liquidity Generation Event, or LGE.
  • Each LGE initially lasts for 90 days.
  • Once the LGE starts, users can deposit tokens or WETH in exchange for a future share of underlying LP assets once the LGE has ended.
  • An address registered as a Protocol in the associated Reader contract can also enter an LGE on behalf of a user.
  • If the whitelist is enabled, only whitelisted users are permitted to enter the LGE.
  • ETH and token deposits are swapped for WETH and stored in the contract.
  • A user's total deposited WETH value cannot exceed the LGE's maximum total deposit amount.
  • Users cannot enter the LGE if the amount of funds raised has reached the hard cap, or if the LGE has been aborted.
  • A user that has deposited can choose to "emergency exit" from the contract up to 5 minutes before its end time, at any time if the LGE has been aborted, or 3 days after the LGE's "sell out" time if it has not been ended by the owner.
  • When emergency exiting, the user is transferred their total deposited ETH amount. Once exited, a user cannot reenter.
  • The owner cannot enter their own LGE.
  • Any deposit into the LGE within its last 5 minutes extends the LGE's end time by an additional 5 minutes.
  • If a deposit results in the LGE reaching its hard cap the LGE is marked as sold out and deposits are disabled. The LGE owner or a Super Admin of the Reader contract can then end the LGE.
  • The LGE owner or a Super Admin of the Reader contract can also end the LGE if the LGE's end time has passed and the soft cap has been reached. If the soft cap was not reached, the owner can choose to extend the end time.
  • Any address can abort the LGE if the soft cap was not reached after its end time, returning reserved SD tokens to the owner and allowing users to withdraw their deposited funds.
  • The owner can also choose to abort before the end time as long as the soft cap has not already been reached.
  • If an LGE is successfully ended, raised ETH and the initially provided SD tokens are used to add backing and liquidity as follows:
    • The backing share of raised WETH is swapped for the SD token's backing token and transferred to the SD Token's BackingLogic address.
    • The FEG share of the remaining WETH is swapped for FEG; these FEG tokens are paired with the FEG share of the contract's initially provided SD tokens and added to the SD:FEG liquidity pool in exchange for minted LP tokens.
    • All remaining WETH is paired with the remaining SD tokens for liquidity and added to the SD:WETH liquidity pool for minted LP tokens.
    • A Dev share of both the SD:FEG LP tokens and SD:WETH LP tokens are added to the owner's vesting balance; the remainder is stored in the contract to be vested to contributors. Token owners should note that the Dev percentage is taken from 98% of each LP token, not the full amount.
  • The address ending the LGE specifies minimum FEG and Backing amounts to receive from these swaps to protect against front running.
  • After liquidity has been provided, users can enable vesting for themselves, which sets their FEG LP and WETH LP vesting amounts based on their share of contributed WETH.
  • LP tokens vest over the contract's total number of rounds, each with a duration initially set by the owner.
  • Users can claim up to 1 round's worth of their vested tokens at once, with a 1 day cooldown.
  • The owner's first claim limit is adjusted based on the contract's dev share to prevent too large of an initial claim.
  • Upon claiming, both SD:WETH and SD:FEG LP tokens are redeemed in exchange for their underlying SD, WETH, and FEG amounts.
  • The resulting SD tokens are burned using the BackingLogic contract; their backing token value is subsequently transferred from the BackingLogic contract to the user.
  • All resulting WETH is swapped for ETH and sent to the user.
  • The contract's resulting FEG is also transferred to the user.
  • Any user or the owner can also choose to lock their claiming ability for a specified number of days.
  • The owner can lock their SD tokens into this contract at any time, which transfers them into this contract until the owner's specified end time has been reached.
  • Multiple vests can be created at once, each with their own end times.
  • The owner can withdraw any vests once their end time has been reached, or instantly if the LGE has been aborted.
  • The owner can withdraw any miscellaneous tokens from the contract if the LGE has either not started or has been aborted, excluding FEG or relevant LP tokens.
  • Additionally, backing tokens and WETH can only be withdrawn by the owner if all user-deposited funds have been withdrawn.
  • Whitelist functionality is automatically disabled once all whitelisted users have entered the LGE.
  • The owner can toggle whitelist functionality before the LGE has started, and can disable the whitelist at any time.
  • The owner can add users to the whitelist before the LGE has started. Whitelist functionality is automatically enabled when users are added to the whitelist.
  • The owner can remove a whitelisted user that is not entered into the LGE from the whitelist before the LGE's start while whitelist functionality is enabled.
  • The owner has the ability to update the following various LGE settings before the LGE has started:
    • The start time can be updated to any time at least 15 minutes from the time of editing. This updates the LGE's end time to correlate with its duration.
    • The dev share and backing shares can be updated to up to 50% each.
    • The hard cap and number of SD tokens to be used for LP can be updated to any values. Updating either of these values will update the soft cap and rate accordingly.
    • When updating the number of SD tokens to be used for LP, the difference is either transferred to or from the owner in order for the contract to hold the correct number of SD tokens.
    • The vesting delay can be set between 2 and 90 days.
    • The number of vesting rounds can be set between 5 and 20.
    • The maximum total deposit amount can be set to at least 0.01 of the blockchain's native token.
  • LGE owners should ensure that the LGE Deployer's range is at least equal to the backing pair's number of recorded ticks at the time of the LGE's end in order to ensure a proper front run check.
BackingLogic Contract:
  • This contract is used to facilitate loans, SD liquidations, and utilize collected backing and liquidity fees.
  • If either of the accumulated liquidity or backing fee values have exceeded their corresponding thresholds, the threshold amount is converted on any non-tax-exempt SD token sale.
  • The backing threshold SD amount is swapped for the SD token's backing asset and stored in this contract to be made available for loans.
  • The liquidity threshold SD amount is split. One half is swapped for WETH and paired with the SD tokens and added as liquidity. Resulting SD:WETH LP tokens are stored in this contract.
  • A maximum of 1% of the SD:WETH liquidity pool's SD token balance can be converted to liquidity at once.
  • Users have the ability to take out loans by depositing SD token collateral into this contract in exchange for backing tokens. This collateral is considered "burned" until withdrawn.
  • Backing token value is calculated as the ratio of backing tokens held by the contract to the total circulating SD token supply, excluding tokens deposited into this contract as collateral and SD tokens held by the associated Bridge contract.
  • A user can only take out one loan at a time.
  • The user can repay any amount of their active loan at no interest in order to receive the proportional share of their collateral back.
  • Users can extend their loan from 30 to 365 days for a fee of 0.003333% per day, which is taken directly from the user's deposited collateral.
  • A loan that has reached its end time must be extended in order to be repaid. Each extension adds time to the loan's current expiry time.
  • Taking out a new loan after defaulting will delete the user's expired loan, removing their ability to extend it.
  • When an expired loan is deleted, originally deposited collateral is transferred to the 0x..dead address.
  • Users can also choose to take a loan of all backing tokens held by the contract by depositing collateral. The amount of each backing token received is proportional to the deposited collateral in relation to the total circulating supply, excluding tokens reserved as collateral.
  • The contract must hold the minimum balance threshold in order for it to be borrowed.
  • A backing fee is taken from all secondary backing token loan amounts and transferred to the Reader contract's associated protocol address.
  • Users can repay any percent of their multi-token loan while it is still active.
  • Instead of taking a loan, users also have the option to simply burn their tokens through this contract in exchange for their backing token value.
  • This contract also supports flash loan functionality, allowing users to withdraw any amount of the contract's backing asset balance as long it is returned by the end of the transaction along with an additional 0.08% of the contract's starting balance as a fee.
  • The SD owner and Super Admin of the associated Reader contract can each add their approval for a backing token migration, BackingLogic upgrade, or backing token update at any time.
  • A backing token migration transfers a specified backing token balance from this contract to a proposed address.
  • A BackingLogic upgrade sets the SD token's associated BackingLogic to a proposed address.
  • A backing token update changes the SD token's associated backing token to a proposed token, and also updates the main backing token within this contract.
  • If the SD token is set as a DAO in the associated Reader contract, only the SD token owner needs to grant approval in order for any of these actions to be executed. Otherwise, both the SD token owner and a Super Admin must both grant approval and propose the same addresses.
  • Any address can trigger these actions once the approval requirements are met.
  • The SD token owner can add a new token to the list of backing assets at any time.
  • A Super Admin of the Reader contract can update permitted slippage used by this contract to any value between 3 and 30%.
  • A Super Admin of the associated Reader contract can update the backing fee to any percentage at any time.
  • A Super Admin of the Reader contract can update the number of ticks averaged for this contract's front running checks to up to 99.

Vulnerability Analysis

Vulnerability Category Notes Result
Arbitrary Jump/Storage Write N/A PASS
Centralization of Control
  • SD Token owners have significant control over tokens and their LGE as described above, including fees, ability to potentially front run swaps, and the Router address used for swaps and LP creation.
  • The project team has control over addresses and settings used during token and LGE creation as described above.
  • WARNING
    Compiler Issues N/A PASS
    Delegate Call to Untrusted Contract N/A PASS
    Dependence on Predictable Variables N/A PASS
    Ether/Token Theft N/A PASS
    Flash Loans N/A PASS
    Front Running N/A PASS
    Improper Events N/A PASS
    Improper Authorization Scheme N/A PASS
    Integer Over/Underflow N/A PASS
    Logical Issues N/A PASS
    Oracle Issues N/A PASS
    Outdated Compiler Version N/A PASS
    Race Conditions N/A PASS
    Reentrancy N/A PASS
    Signature Issues N/A PASS
    Sybil Attack N/A PASS
    Unbounded Loops N/A PASS
    Unused Code N/A PASS
    Overall Contract Safety   PASS

    About SourceHat

    SourceHat has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1800+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
    Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!

    Contact us today to get a free quote for a smart contract audit of your project!

    What is a SourceHat Audit?

    Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.

    How Do I Interpret the Findings?

    Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:

    • High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
    • Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
    • Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
    • Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.