-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add farm #13
base: dev
Are you sure you want to change the base?
Add farm #13
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
|
||
import "./libraries/SafeToken.sol"; | ||
import "./IFarm.sol"; | ||
import "../core/interfaces/IERC20.sol"; | ||
|
||
contract Farm is IFarm, OwnableUpgradeable, PausableUpgradeable { | ||
using SafeToken for address; | ||
|
||
struct StakeInfo { | ||
uint256 amount; | ||
uint256 rewardDebt; | ||
} | ||
|
||
struct PoolInfo { | ||
uint256 allocPoint; | ||
uint256 lastRewardBlock; | ||
uint256 accRewardPerShare; | ||
} | ||
|
||
address public rewardToken; | ||
|
||
uint256 public rewardPerBlock; | ||
uint256 public totalAllocPoint; | ||
Comment on lines
+25
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets make next improvement - have those details not at contract level, but for each particular pool |
||
|
||
mapping(address => PoolInfo) public poolInfos; | ||
mapping(address => mapping(address => StakeInfo)) public stakes; | ||
mapping(address => uint256) private lpTokenBalances; | ||
|
||
event LogPoolAdded(address indexed lpToken, uint256 allocPoint); | ||
event LogPoolAllocationUpdate(address indexed lpToken, uint256 allocPoint); | ||
event LogPoolRewardUpdate( | ||
address indexed lpToken, | ||
uint256 accRewardPerShare, | ||
uint256 lastRewardBlock | ||
); | ||
event LogDeposit(address indexed user, address indexed lpToken, uint256 amount); | ||
event LogWithdraw(address indexed user, address indexed lpToken, uint256 amount); | ||
event LogRewardPaid(address indexed user, address indexed lpToken, uint256 reward); | ||
|
||
function initialize( | ||
address _rewardToken, | ||
uint256 _rewardPerBlock | ||
) external initializer { | ||
OwnableUpgradeable.__Ownable_init(); | ||
PausableUpgradeable.__Pausable_init(); | ||
|
||
rewardToken = _rewardToken; | ||
rewardPerBlock = _rewardPerBlock; | ||
totalAllocPoint = 0; | ||
} | ||
|
||
function addPool( | ||
address _lpToken, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also need to check for lpToken. minimum that it is contract. better - to verify token interface if thats possible |
||
uint256 _allocPoint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can it be zero? if no - need also to check it |
||
) external override onlyOwner whenNotPaused { | ||
poolInfos[_lpToken] = PoolInfo({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to check if pool already exists. Otherwise we may override existing one with that action |
||
allocPoint: _allocPoint, | ||
lastRewardBlock: block.number, | ||
accRewardPerShare: 0 | ||
}); | ||
|
||
totalAllocPoint += _allocPoint; | ||
|
||
emit LogPoolAdded(_lpToken, _allocPoint); | ||
} | ||
|
||
function updateAllocation( | ||
address _lpToken, | ||
uint256 _allocPoint | ||
) external override onlyOwner whenNotPaused { | ||
PoolInfo storage pool = poolInfos[_lpToken]; | ||
|
||
totalAllocPoint = totalAllocPoint + _allocPoint - pool.allocPoint; | ||
pool.allocPoint = _allocPoint; | ||
|
||
emit LogPoolAllocationUpdate(_lpToken, _allocPoint); | ||
} | ||
|
||
function deposit(address _lpToken, uint256 _amount) external override whenNotPaused { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to have reentrancy protection for this function because it has external calls |
||
require(_lpToken != address(0), "Zero address"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check for being contract is must, additionally if thats tech possible - check for interface |
||
require(_amount > 0, "Zero amount"); | ||
|
||
updatePoolReward(_lpToken); | ||
|
||
PoolInfo storage pool = poolInfos[_lpToken]; | ||
StakeInfo storage stake = stakes[_lpToken][msg.sender]; | ||
|
||
if (stake.amount > 0) { | ||
uint256 pending = (stake.amount * pool.accRewardPerShare) / | ||
1e12 - | ||
stake.rewardDebt; | ||
if (pending > 0) { | ||
address(rewardToken).safeTransfer(msg.sender, pending); | ||
emit LogRewardPaid(msg.sender, _lpToken, pending); | ||
} | ||
Comment on lines
+97
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if reward payment on deposit is understandable behaviour for the user. we aren't making restaking of the rewards, but lets make them withdrawn in a separate function |
||
} | ||
|
||
stake.amount += _amount; | ||
stake.rewardDebt = (stake.amount * pool.accRewardPerShare) / 1e12; | ||
|
||
_lpToken.safeTransferFrom(msg.sender, address(this), _amount); | ||
lpTokenBalances[_lpToken] += _amount; | ||
|
||
emit LogDeposit(msg.sender, _lpToken, _amount); | ||
} | ||
|
||
function withdraw( | ||
address _lpToken, | ||
uint256 _amount | ||
) external override whenNotPaused { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reentrancy protection |
||
require(_lpToken != address(0), "Zero address"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same need more verification for input |
||
PoolInfo storage pool = poolInfos[_lpToken]; | ||
StakeInfo storage stake = stakes[_lpToken][msg.sender]; | ||
|
||
require(stake.amount >= _amount, "Not sufficient amount"); | ||
|
||
updatePoolReward(_lpToken); | ||
|
||
uint256 pending = (stake.amount * pool.accRewardPerShare) / | ||
1e12 - | ||
stake.rewardDebt; | ||
if (pending > 0) { | ||
address(rewardToken).safeTransfer(msg.sender, pending); | ||
emit LogRewardPaid(msg.sender, _lpToken, pending); | ||
} | ||
Comment on lines
+127
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, lets think about making reward withdraw in separate function |
||
|
||
if (_amount > 0) { | ||
stake.amount -= _amount; | ||
_lpToken.safeTransfer(msg.sender, _amount); | ||
lpTokenBalances[_lpToken] -= _amount; | ||
} | ||
|
||
stake.rewardDebt = (stake.amount * pool.accRewardPerShare) / 1e12; | ||
|
||
emit LogWithdraw(msg.sender, _lpToken, _amount); | ||
} | ||
|
||
function emergencyWithdraw(address _lpToken) override external whenPaused { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reentrancy protection also here |
||
StakeInfo storage stake = stakes[_lpToken][msg.sender]; | ||
require(stake.amount > 0, "Zero balance"); | ||
|
||
uint256 amount = stake.amount; | ||
|
||
stake.amount = 0; | ||
stake.rewardDebt = 0; | ||
|
||
_lpToken.safeTransfer(msg.sender, amount); | ||
lpTokenBalances[_lpToken] -= amount; | ||
|
||
emit LogWithdraw(msg.sender, _lpToken, amount); | ||
} | ||
|
||
function setRewardPerBlock(uint256 _rewardPerBlock) override external onlyOwner whenNotPaused { | ||
rewardPerBlock = _rewardPerBlock; | ||
} | ||
|
||
function pause() external override onlyOwner { | ||
_pause(); | ||
} | ||
|
||
|
||
function unpause() external override onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
|
||
function updatePoolReward(address _lpToken) public override { | ||
PoolInfo storage pool = poolInfos[_lpToken]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we won't have here revert for zero token for example. Thats why I recommend to move all code of this function to internal function _updatePoolReward. And add verifications to updatePoolReward. It will allow to keep code protected when _updatePoolReward is called externally in updatePoolReward and do not duplicate verifications when _updatePoolReward is called internally for example in deposit function |
||
|
||
if (block.number <= pool.lastRewardBlock) { | ||
return; | ||
} | ||
|
||
uint256 lpSupply = lpTokenBalances[_lpToken]; | ||
|
||
if (lpSupply == 0) { | ||
pool.lastRewardBlock = block.number; | ||
return; | ||
} | ||
|
||
uint256 multiplier = block.number - pool.lastRewardBlock; | ||
uint256 reward = (multiplier * rewardPerBlock * pool.allocPoint) / totalAllocPoint; | ||
pool.accRewardPerShare += (reward * 1e12) / lpSupply; | ||
pool.lastRewardBlock = block.number; | ||
|
||
emit LogPoolRewardUpdate( | ||
_lpToken, | ||
pool.accRewardPerShare, | ||
pool.lastRewardBlock | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
interface IFarm { | ||
function addPool(address _lpToken, uint _allocPoint) external; | ||
|
||
function updateAllocation(address _lpToken, uint _allocPoint) external; | ||
|
||
function deposit(address _lpToken, uint256 _amount) external; | ||
|
||
function withdraw(address _lpToken, uint256 _amount) external; | ||
|
||
function emergencyWithdraw(address _lpToken) external; | ||
|
||
function setRewardPerBlock(uint256 _rewardPerBlock) external; | ||
|
||
function pause() external; | ||
|
||
function unpause() external; | ||
|
||
function updatePoolReward(address _lpToken) external; | ||
|
||
function rewardPerBlock() external view returns (uint); | ||
|
||
function totalAllocPoint() external view returns (uint); | ||
|
||
function rewardToken() external view returns (address); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
interface ERC20Interface { | ||
function balanceOf(address user) external view returns (uint256); | ||
} | ||
|
||
library SafeToken { | ||
function myBalance(address token) internal view returns (uint256) { | ||
return ERC20Interface(token).balanceOf(address(this)); | ||
} | ||
|
||
function balanceOf(address token, address user) internal view returns (uint256) { | ||
return ERC20Interface(token).balanceOf(user); | ||
} | ||
|
||
function safeApprove(address token, address to, uint256 value) internal { | ||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); // bytes4(keccak256(bytes('approve(address,uint256)'))); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeApprove"); | ||
} | ||
|
||
function safeTransfer(address token, address to, uint256 value) internal { | ||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); // bytes4(keccak256(bytes('transfer(address,uint256)'))); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransfer"); | ||
} | ||
|
||
function safeTransferFrom(address token, address from, address to, uint256 value) internal { | ||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransferFrom"); | ||
} | ||
|
||
function safeTransferETH(address to, uint256 value) internal { | ||
(bool success, ) = to.call{ value: value }(new bytes(0)); | ||
require(success, "!safeTransferETH"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const { ethers , upgrades} = require("hardhat"); | ||
|
||
const rewardAddress = '0x0000000000000000000000000000000000000000'; | ||
const rewardPerBLock = '0'; | ||
|
||
async function main() { | ||
let admin; | ||
|
||
[admin] = await ethers.getSigners(); | ||
|
||
console.log("Deploying contracts with the account:", admin.address); | ||
console.log("Account balance:", (await admin.getBalance()).toString()); | ||
|
||
const Farm = await ethers.getContractFactory("Farm", admin); | ||
const farm = await upgrades.deployProxy(Farm, [rewardAddress, rewardPerBLock]); | ||
await farm.deployed(); | ||
|
||
console.log("Farm address: " + farm.address); | ||
} | ||
|
||
main() | ||
.then(() => process.exit(0)) | ||
.catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can rename it to FarmFactory