Skip to content
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

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions contracts/farm/Farm.sol
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 {
Copy link
Contributor

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

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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,
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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({
Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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");
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reentrancy protection

require(_lpToken != address(0), "Zero address");
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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];
Copy link
Contributor

Choose a reason for hiding this comment

The 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
);
}
}
28 changes: 28 additions & 0 deletions contracts/farm/IFarm.sol
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);
}
36 changes: 36 additions & 0 deletions contracts/farm/libraries/SafeToken.sol
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");
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"license": "GPL-3.0",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
"@openzeppelin/contracts": "^4.0.0",
"@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/contracts-upgradeable": "^4.7.3",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"dotenv": "^16.0.1",
Expand Down
26 changes: 26 additions & 0 deletions scripts/deploy-farm.js
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);
});
Loading