Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Storage Write | N/A | PASS |
Arbitrary Jump | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Deprecated Opcodes | N/A | PASS |
Ether Thief | N/A | PASS |
Exceptions | N/A | PASS |
External Calls | N/A | PASS |
Flash Loans | The Lock contract uses an insecure Oracle implementation. | WARNING |
Integer Over/Underflow | N/A | PASS |
Multiple Sends | N/A | PASS |
Suicide | N/A | PASS |
State Change External Calls | N/A | Pass |
Unchecked Retval | N/A | PASS |
User Supplied Assertion | N/A | PASS |
Critical Solidity Compiler | N/A | PASS |
Overall Contract Safety | WARNING |
DeFi Hodl Token - Smart Contract Audit Report
Summary
DeFi Hodl is a new project on the Ethereum blockchain.
We audited DeFi Hodl's contracts at commit f352111ffe047f7ca8467e8590864410f358416c on Github. Once deployed, we will verify the code and add the mainnet addresses here.
Notes on the Token Contract:The total supply of the token is 10 million tokens, all of which will be provided to the team upon deployment. No minting or burning functions are present; and there is no fee on transfers. No ownership-restricted functions exist The contract uses compiler version ^0.8.0 which has built-in overflow protection, replacing the need for SafeMath.
Notes on the Lock Contract:This contract allows users to lock tokens either for a duration set by the user or until the value of the token reaches a price set by the user. Users can bypass the lock by calling emergencyUnlock, which charges a seperate fee set by the team, but sends all of the locked tokens (minus the fee) back to the user. A fee is charged by the team upon both locking and unlocking tokens; and emergencyUnlocks as well. If a user has a number of DeFi Hodl tokens set by the team, they will be exempt from these fees. The contract looks to Uniswap's getAmountsOut() function to determine the value of the token. This implementation is vulnerable to price manipulation via flash loan, potentially allowing a user to extract tokens from their deposit earlier than what should be allowed. Assuming there is not an extremely large amount of value locked by a single user, and the emergencyUnlock fees are reasonable, this attack vector is unlikely to be exploited. Therefore, the team has elected not to fix this issue. The team can update the fee at any time up to a hard-coded limit of 1.5%. The contract uses compiler version ^0.8.0 which has built-in overflow protection, replacing the need for SafeMath.
Audit Findings Summary:No security issues from external attackers were identified in the token contract. The price-lookup mechanism employed in the Lock contract can be manipulated by an attacker. As with any presale, ensure trust prior to sending ETH to the team. Date: February 23rd, 2021 Update Date: February 23rd, 2021 - Implemented some recommendations from audit.
Token Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ Context
- [Int] _msgSender
- [Int] _msgData
+ [Int] IERC20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ ERC20 (Context, IERC20)
- [Pub] #
- [Pub] name
- [Pub] symbol
- [Pub] decimals
- [Pub] totalSupply
- [Pub] balanceOf
- [Pub] transfer #
- [Pub] allowance
- [Pub] approve #
- [Pub] transferFrom #
- [Pub] increaseAllowance #
- [Pub] decreaseAllowance #
- [Int] _transfer #
- [Int] _approve #
Click here to download the source code as a .sol file.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract ERC20 is Context, IERC20 {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply = 10000000 * 10 ** 18; // 10m
string private _name = "DeFi Hodl";
string private _symbol = "iHODL";
constructor () {
_balances[_msgSender()] = _totalSupply;
}
function name() public view virtual returns (string memory) {
return _name;
}
function symbol() public view virtual returns (string memory) {
return _symbol;
}
function decimals() public view virtual returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
_approve(sender, _msgSender(), currentAllowance - amount);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
_balances[sender] = senderBalance - amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
}
Lock Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IERC20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ [Int] Uniswap
- [Ext] getAmountsOut
+ Ownable
- [Pub] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ TokenLock (Ownable)
- [Pub] #
- [Pub] getLock
- [Pub] getTokenPrice
- [Pub] isClaimable
- [Pub] calculateFee
- [Pub] lock #
- [Pub] unlock #
- [Pub] emergencyUnlock #
- [Pub] setEmergencyUnlockValues #
- modifiers: onlyOwner
- [Pub] setFee #
- modifiers: onlyOwner
- [Pub] setMinHodl #
- modifiers: onlyOwner
Click here to download the source code as a .sol file.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface Uniswap {
function getAmountsOut(uint amountIn, address[] memory path)
external
view
returns (uint[] memory amounts);
}
abstract contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor () {
address msgSender = msg.sender;
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
contract TokenLock is Ownable {
struct Lock {
address user;
address token;
uint256 amount;
uint256 maxUnlockTime;
address tradeToken;
uint256 minUnlockPrice;
bool claimed;
}
event LockEvent(address indexed user, address indexed token, uint256 amount, uint256 maxUnlockTime, address tradeToken, uint256 minUnlockPrice, uint256 id);
event UnlockEvent(uint256 id);
mapping(uint256 => Lock) public locks;
uint256 public lockId;
address public uniswapAddress = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address public wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public iHodlTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // TODO change to real token
uint256 public currentFee;
uint256 public minHodl;
uint256 public emergencyUnlockCosts;
constructor(uint256 _currentFee, uint256 _minHodl, uint256 _emergencyUnlockCosts) {
currentFee = _currentFee;
minHodl = _minHodl;
emergencyUnlockCosts = _emergencyUnlockCosts;
}
function getLock(uint256 _id) public view virtual returns (Lock memory) {
return locks[_id];
}
function getTokenPrice(address _token, uint256 _amount, address _tradeToken) public view virtual returns (uint256) {
if (_tradeToken == wethAddress) {
address[] memory path = new address[](2);
path[0] = _token;
path[1] = wethAddress;
return Uniswap(uniswapAddress).getAmountsOut(_amount, path)[1];
} else {
address[] memory path = new address[](3);
path[0] = _token;
path[1] = wethAddress;
path[2] = _tradeToken;
return Uniswap(uniswapAddress).getAmountsOut(_amount, path)[2];
}
}
function isClaimable(uint256 _lockId) public view virtual returns (bool) {
Lock memory lock_ = locks[_lockId];
if (lock_.claimed) {
return false;
} else if (lock_.maxUnlockTime <= block.timestamp) {
return true;
} else if (lock_.minUnlockPrice <= getTokenPrice(lock_.token, lock_.amount, lock_.tradeToken)) {
return true;
} else {
return false;
}
}
function calculateFee(uint256 _amount, address _account) public view virtual returns (uint256) {
if (IERC20(iHodlTokenAddress).balanceOf(_account) >= minHodl) {
return 0;
} else {
return _amount * 100 / 1000 * currentFee;
}
}
function lock(address _token, uint256 _amount, uint256 _maxDuration, address _tradeToken, uint256 _minUnlockPrice) public virtual {
require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "transferFrom failed");
uint256 maxUnlockTime_ = block.timestamp + _maxDuration;
uint256 fee_ = calculateFee(_amount, msg.sender);
if (fee_ > 0) {
require(IERC20(_token).transfer(owner(), fee_), "fee transfer failed");
_amount = _amount - fee_;
}
Lock memory lock_ = Lock({
user:msg.sender,
token:_token,
amount:_amount,
maxUnlockTime:maxUnlockTime_,
tradeToken:_tradeToken,
minUnlockPrice:_minUnlockPrice,
claimed:false
});
lockId += 1;
locks[lockId] = lock_;
emit LockEvent(msg.sender, _token, _amount, maxUnlockTime_, _tradeToken, _minUnlockPrice, lockId);
}
function unlock(uint256 _lockId) public virtual {
Lock memory lock_ = locks[_lockId];
uint256 amount_ = lock_.amount;
uint256 fee_ = calculateFee(amount_, msg.sender);
if (fee_ > 0) {
require(IERC20(lock_.token).transfer(owner(), fee_), "fee transfer failed");
amount_ = amount_ - fee_;
}
locks[_lockId].claimed = true;
require(msg.sender == lock_.user, "msg.sender does not own this lock");
require(isClaimable(_lockId), "lock not claimable");
require(IERC20(lock_.token).transfer(lock_.user, amount_), "transfer failed");
emit UnlockEvent(_lockId);
}
function emergencyUnlock(uint256 _lockId) public virtual {
Lock memory lock_ = locks[_lockId];
locks[_lockId].claimed = true;
require(msg.sender == lock_.user, "msg.sender does not own this lock");
require(IERC20(iHodlTokenAddress).transferFrom(msg.sender, owner(), emergencyUnlockCosts), "emergencyUnlock payment failed");
require(IERC20(lock_.token).transfer(lock_.user, lock_.amount), "transfer failed");
}
function setEmergencyUnlockValues(address _address, uint256 _amount) public virtual onlyOwner {
iHodlTokenAddress = _address;
emergencyUnlockCosts = _amount;
}
function setFee(uint256 _fee) public virtual onlyOwner {
currentFee = _fee; // 1 means 0.1%
}
function setMinHodl(uint256 _minHodl) public virtual onlyOwner {
minHodl = _minHodl;
}
}