文件 1 的 1:FarmMain.sol
pragma solidity ^0.7.6;
struct LiquidityPoolData {
address liquidityPoolAddress;
uint256 amount;
address tokenAddress;
bool amountIsLiquidityPool;
bool involvingETH;
address receiver;
}
struct SwapData {
bool enterInETH;
bool exitInETH;
address[] liquidityPoolAddresses;
address[] path;
address inputToken;
uint256 amount;
address receiver;
}
pragma solidity ^0.7.6;
pragma abicoder v2;
interface IAMM {
event NewLiquidityPoolAddress(address indexed);
function info() external view returns(string memory name, uint256 version);
function data() external view returns(address ethereumAddress, uint256 maxTokensPerLiquidityPool, bool hasUniqueLiquidityPools);
function balanceOf(address liquidityPoolAddress, address owner) external view returns(uint256, uint256[] memory, address[] memory);
function byLiquidityPool(address liquidityPoolAddress) external view returns(uint256, uint256[] memory, address[] memory);
function byTokens(address[] calldata liquidityPoolTokens) external view returns(uint256, uint256[] memory, address, address[] memory);
function byPercentage(address liquidityPoolAddress, uint256 numerator, uint256 denominator) external view returns (uint256, uint256[] memory, address[] memory);
function byLiquidityPoolAmount(address liquidityPoolAddress, uint256 liquidityPoolAmount) external view returns(uint256[] memory, address[] memory);
function byTokenAmount(address liquidityPoolAddress, address tokenAddress, uint256 tokenAmount) external view returns(uint256, uint256[] memory, address[] memory);
function createLiquidityPoolAndAddLiquidity(address[] calldata tokenAddresses, uint256[] calldata amounts, bool involvingETH, address receiver) external payable returns(uint256, uint256[] memory, address, address[] memory);
function addLiquidity(LiquidityPoolData calldata data) external payable returns(uint256, uint256[] memory, address[] memory);
function addLiquidityBatch(LiquidityPoolData[] calldata data) external payable returns(uint256[] memory, uint256[][] memory, address[][] memory);
function removeLiquidity(LiquidityPoolData calldata data) external returns(uint256, uint256[] memory, address[] memory);
function removeLiquidityBatch(LiquidityPoolData[] calldata data) external returns(uint256[] memory, uint256[][] memory, address[][] memory);
function getSwapOutput(address tokenAddress, uint256 tokenAmount, address[] calldata, address[] calldata path) view external returns(uint256[] memory);
function swapLiquidity(SwapData calldata data) external payable returns(uint256);
function swapLiquidityBatch(SwapData[] calldata data) external payable returns(uint256[] memory);
}
pragma solidity ^0.7.6;
struct FarmingPositionRequest {
uint256 setupIndex;
uint256 amount;
bool amountIsLiquidityPool;
address positionOwner;
}
struct FarmingSetupConfiguration {
bool add;
bool disable;
uint256 index;
FarmingSetupInfo info;
}
struct FarmingSetupInfo {
bool free;
uint256 blockDuration;
uint256 originalRewardPerBlock;
uint256 minStakeable;
uint256 maxStakeable;
uint256 renewTimes;
address ammPlugin;
address liquidityPoolTokenAddress;
address mainTokenAddress;
address ethereumAddress;
bool involvingETH;
uint256 penaltyFee;
uint256 setupsCount;
uint256 lastSetupIndex;
}
struct FarmingSetup {
uint256 infoIndex;
bool active;
uint256 startBlock;
uint256 endBlock;
uint256 lastUpdateBlock;
uint256 objectId;
uint256 rewardPerBlock;
uint256 totalSupply;
}
struct FarmingPosition {
address uniqueOwner;
uint256 setupIndex;
uint256 creationBlock;
uint256 liquidityPoolTokenAmount;
uint256 mainTokenAmount;
uint256 reward;
uint256 lockedRewardPerBlock;
}
pragma solidity ^0.7.6;
interface IFarmMain {
function ONE_HUNDRED() external view returns(uint256);
function _rewardTokenAddress() external view returns(address);
function position(uint256 positionId) external view returns (FarmingPosition memory);
function setups() external view returns (FarmingSetup[] memory);
function setup(uint256 setupIndex) external view returns (FarmingSetup memory, FarmingSetupInfo memory);
function setFarmingSetups(FarmingSetupConfiguration[] memory farmingSetups) external;
function openPosition(FarmingPositionRequest calldata request) external payable returns(uint256 positionId);
function addLiquidity(uint256 positionId, FarmingPositionRequest calldata request) external payable;
}
pragma solidity ^0.7.6;
interface IFarmExtension {
function init(bool byMint, address host, address treasury) external;
function setHost(address host) external;
function setTreasury(address treasury) external;
function data() external view returns(address farmMainContract, bool byMint, address host, address treasury, address rewardTokenAddress);
function transferTo(uint256 amount) external;
function backToYou(uint256 amount) external payable;
function setFarmingSetups(FarmingSetupConfiguration[] memory farmingSetups) external;
}
pragma solidity ^0.7.6;
interface IFarmFactory {
event ExtensionCloned(address indexed);
function feePercentageInfo() external view returns (uint256, address);
function farmDefaultExtension() external view returns(address);
function cloneFarmDefaultExtension() external returns(address);
function getFarmTokenCollectionURI() external view returns (string memory);
function getFarmTokenURI() external view returns (string memory);
}
pragma solidity ^0.7.6;
abstract contract ERC1155Receiver {
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
mapping(bytes4 => bool) private _supportedInterfaces;
constructor() {
_registerInterface(_INTERFACE_ID_ERC165);
_registerInterface(
ERC1155Receiver(0).onERC1155Received.selector ^
ERC1155Receiver(0).onERC1155BatchReceived.selector
);
}
function supportsInterface(bytes4 interfaceId) public view returns (bool) {
return _supportedInterfaces[interfaceId];
}
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
)
external
virtual
returns(bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
)
external
virtual
returns(bytes4);
}
pragma solidity ^0.7.6;
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 safeApprove(address spender, uint256 amount) external;
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function decimals() external view returns (uint8);
}
pragma solidity ^0.7.6;
interface IEthItemOrchestrator {
function createNative(bytes calldata modelInitPayload, string calldata ens)
external
returns (address newNativeAddress, bytes memory modelInitCallResponse);
}
pragma solidity ^0.7.6;
interface IERC1155 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}
pragma solidity ^0.7.6;
interface IEthItemInteroperableInterface is IERC20 {
function mainInterface() external view returns (address);
function objectId() external view returns (uint256);
function mint(address owner, uint256 amount) external;
function burn(address owner, uint256 amount) external;
function permitNonce(address sender) external view returns(uint256);
function permit(address owner, address spender, uint value, uint8 v, bytes32 r, bytes32 s) external;
function interoperableInterfaceVersion() external pure returns(uint256 ethItemInteroperableInterfaceVersion);
}
pragma solidity ^0.7.6;
interface IEthItem is IERC1155 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply(uint256 objectId) external view returns (uint256);
function name(uint256 objectId) external view returns (string memory);
function symbol(uint256 objectId) external view returns (string memory);
function decimals(uint256 objectId) external view returns (uint256);
function uri(uint256 objectId) external view returns (string memory);
function mainInterfaceVersion() external pure returns(uint256 ethItemInteroperableVersion);
function toInteroperableInterfaceAmount(uint256 objectId, uint256 ethItemAmount) external view returns (uint256 interoperableInterfaceAmount);
function toMainInterfaceAmount(uint256 objectId, uint256 erc20WrapperAmount) external view returns (uint256 mainInterfaceAmount);
function interoperableInterfaceModel() external view returns (address, uint256);
function asInteroperable(uint256 objectId) external view returns (IEthItemInteroperableInterface);
function emitTransferSingleEvent(address sender, address from, address to, uint256 objectId, uint256 amount) external;
function mint(uint256 amount, string calldata partialUri)
external
returns (uint256, address);
function burn(
uint256 objectId,
uint256 amount
) external;
function burnBatch(
uint256[] calldata objectIds,
uint256[] calldata amounts
) external;
}
pragma solidity ^0.7.6;
interface INativeV1 is IEthItem {
function init(string calldata name, string calldata symbol, bool hasDecimals, string calldata collectionUri, address extensionAddress, bytes calldata extensionInitPayload) external returns(bytes memory extensionInitCallResponse);
function extension() external view returns (address extensionAddress);
function canMint(address operator) external view returns (bool result);
function isEditable(uint256 objectId) external view returns (bool result);
function releaseExtension() external;
function uri() external view returns (string memory);
function decimals() external view returns (uint256);
function mint(uint256 amount, string calldata tokenName, string calldata tokenSymbol, string calldata objectUri, bool editable) external returns (uint256 objectId, address tokenAddress);
function mint(uint256 amount, string calldata tokenName, string calldata tokenSymbol, string calldata objectUri) external returns (uint256 objectId, address tokenAddress);
function mint(uint256 objectId, uint256 amount) external;
function makeReadOnly(uint256 objectId) external;
function setUri(string calldata newUri) external;
function setUri(uint256 objectId, string calldata newUri) external;
}
pragma solidity ^0.7.6;
contract FarmMain is IFarmMain, ERC1155Receiver {
uint256 public override constant ONE_HUNDRED = 1e18;
event RewardToken(address indexed rewardTokenAddress);
event Transfer(uint256 indexed positionId, address indexed from, address indexed to);
event SetupToken(address indexed mainToken, address indexed involvedToken);
event FarmToken(uint256 indexed objectId, address indexed liquidityPoolToken, uint256 setupIndex, uint256 endBlock);
address public _factory;
address public _extension;
address public override _rewardTokenAddress;
address public _farmTokenCollection;
mapping(uint256 => FarmingSetupInfo) private _setupsInfo;
uint256 public _farmingSetupsInfoCount;
mapping(uint256 => FarmingSetup) private _setups;
uint256 public _farmingSetupsCount;
mapping(uint256 => FarmingPosition) private _positions;
mapping(uint256 => uint256) private _rewardPerTokenPerSetup;
mapping(uint256 => uint256) private _rewardPerTokenPaid;
mapping(uint256 => uint256) public _partiallyRedeemed;
mapping(uint256 => uint256) private _objectIdSetup;
mapping(uint256 => uint256) private _setupPositionsCount;
mapping(uint256 => uint256) public _rewardReceived;
mapping(uint256 => uint256) public _rewardPaid;
modifier byExtension() {
require(msg.sender == _extension, "Unauthorized");
_;
}
modifier byPositionOwner(uint256 positionId) {
require(_positions[positionId].uniqueOwner == msg.sender && _positions[positionId].creationBlock != 0, "Not owned");
_;
}
modifier activeSetupOnly(uint256 setupIndex) {
require(_setups[setupIndex].active, "Setup not active");
require(_setups[setupIndex].startBlock <= block.number && _setups[setupIndex].endBlock > block.number, "Invalid setup");
_;
}
receive() external payable {}
function init(address extension, bytes memory extensionInitData, address orchestrator, address rewardTokenAddress, bytes memory farmingSetupInfosBytes) public returns(bytes memory extensionReturnCall) {
require(_factory == address(0), "Already initialized");
require((_extension = extension) != address(0), "extension");
_factory = msg.sender;
emit RewardToken(_rewardTokenAddress = rewardTokenAddress);
if (keccak256(extensionInitData) != keccak256("")) {
extensionReturnCall = _call(_extension, extensionInitData);
}
(_farmTokenCollection,) = IEthItemOrchestrator(orchestrator).createNative(abi.encodeWithSignature("init(string,string,bool,string,address,bytes)", "Covenants Farming", "cFARM", true, IFarmFactory(_factory).getFarmTokenCollectionURI(), address(this), ""), "");
if(farmingSetupInfosBytes.length > 0) {
FarmingSetupInfo[] memory farmingSetupInfos = abi.decode(farmingSetupInfosBytes, (FarmingSetupInfo[]));
for(uint256 i = 0; i < farmingSetupInfos.length; i++) {
_setOrAddFarmingSetupInfo(farmingSetupInfos[i], true, false, 0);
}
}
}
function setFarmingSetups(FarmingSetupConfiguration[] memory farmingSetups) public override byExtension {
for (uint256 i = 0; i < farmingSetups.length; i++) {
_setOrAddFarmingSetupInfo(farmingSetups[i].info, farmingSetups[i].add, farmingSetups[i].disable, farmingSetups[i].index);
}
}
function position(uint256 positionId) public override view returns (FarmingPosition memory) {
return _positions[positionId];
}
function setup(uint256 setupIndex) public override view returns (FarmingSetup memory, FarmingSetupInfo memory) {
return (_setups[setupIndex], _setupsInfo[_setups[setupIndex].infoIndex]);
}
function setups() public override view returns (FarmingSetup[] memory) {
FarmingSetup[] memory farmingSetups = new FarmingSetup[](_farmingSetupsCount);
for (uint256 i = 0; i < _farmingSetupsCount; i++) {
farmingSetups[i] = _setups[i];
}
return farmingSetups;
}
function activateSetup(uint256 setupInfoIndex) public {
require(_setupsInfo[setupInfoIndex].renewTimes > 0 && !_setups[_setupsInfo[setupInfoIndex].lastSetupIndex].active, "Invalid toggle.");
_toggleSetup(_setupsInfo[setupInfoIndex].lastSetupIndex);
}
function transferPosition(address to, uint256 positionId) public byPositionOwner(positionId) {
FarmingPosition memory pos = _positions[positionId];
require(
to != address(0) &&
pos.creationBlock != 0,
"Invalid position"
);
uint256 newPositionId = uint256(keccak256(abi.encode(to, _setupsInfo[_setups[pos.setupIndex].infoIndex].free ? 0 : block.number, pos.setupIndex)));
require(_positions[newPositionId].creationBlock == 0, "Invalid transfer");
_positions[newPositionId] = abi.decode(abi.encode(pos), (FarmingPosition));
_positions[newPositionId].uniqueOwner = to;
delete _positions[positionId];
emit Transfer(newPositionId, msg.sender, to);
}
function openPosition(FarmingPositionRequest memory request) public override payable activeSetupOnly(request.setupIndex) returns(uint256 positionId) {
FarmingSetup storage chosenSetup = _setups[request.setupIndex];
address uniqueOwner = (request.positionOwner != address(0)) ? request.positionOwner : msg.sender;
positionId = uint256(keccak256(abi.encode(uniqueOwner, _setupsInfo[chosenSetup.infoIndex].free ? 0 : block.number, request.setupIndex)));
require(_positions[positionId].creationBlock == 0, "Invalid open");
(LiquidityPoolData memory liquidityPoolData, uint256 mainTokenAmount) = _addLiquidity(request.setupIndex, request);
uint256 reward;
uint256 lockedRewardPerBlock;
if (!_setupsInfo[chosenSetup.infoIndex].free) {
(reward, lockedRewardPerBlock) = calculateLockedFarmingReward(request.setupIndex, mainTokenAmount, false, 0);
require(reward > 0 && lockedRewardPerBlock > 0, "Insufficient staked amount");
chosenSetup.totalSupply = chosenSetup.totalSupply + mainTokenAmount;
chosenSetup.lastUpdateBlock = block.number;
_mintFarmTokenAmount(uniqueOwner, liquidityPoolData.amount, request.setupIndex);
} else {
_updateFreeSetup(request.setupIndex, liquidityPoolData.amount, positionId, false);
}
_positions[positionId] = FarmingPosition({
uniqueOwner: uniqueOwner,
setupIndex : request.setupIndex,
liquidityPoolTokenAmount: liquidityPoolData.amount,
mainTokenAmount: mainTokenAmount,
reward: reward,
lockedRewardPerBlock: lockedRewardPerBlock,
creationBlock: block.number
});
_setupPositionsCount[request.setupIndex] += (1 + (_setupsInfo[chosenSetup.infoIndex].free ? 0 : liquidityPoolData.amount));
emit Transfer(positionId, address(0), uniqueOwner);
}
function addLiquidity(uint256 positionId, FarmingPositionRequest memory request) public override payable activeSetupOnly(request.setupIndex) byPositionOwner(positionId) {
FarmingPosition storage farmingPosition = _positions[positionId];
FarmingSetup storage chosenSetup = _setups[farmingPosition.setupIndex];
require(_setupsInfo[chosenSetup.infoIndex].free, "Invalid add liquidity");
(LiquidityPoolData memory liquidityPoolData,) = _addLiquidity(farmingPosition.setupIndex, request);
_rewardPerTokenPerSetup[farmingPosition.setupIndex] += (((block.number - chosenSetup.lastUpdateBlock) * chosenSetup.rewardPerBlock) * 1e18) / chosenSetup.totalSupply;
farmingPosition.reward = calculateFreeFarmingReward(positionId, false);
_rewardPerTokenPaid[positionId] = _rewardPerTokenPerSetup[farmingPosition.setupIndex];
farmingPosition.liquidityPoolTokenAmount += liquidityPoolData.amount;
chosenSetup.lastUpdateBlock = block.number;
chosenSetup.totalSupply += liquidityPoolData.amount;
}
function withdrawReward(uint256 positionId) public byPositionOwner(positionId) {
FarmingPosition storage farmingPosition = _positions[positionId];
uint256 reward = farmingPosition.reward;
uint256 currentBlock = block.number;
if (!_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].free) {
require(farmingPosition.reward > 0, "No reward");
(reward,) = calculateLockedFarmingReward(0, 0, true, positionId);
require(reward <= farmingPosition.reward, "Reward is bigger than expected");
farmingPosition.reward = currentBlock >= _setups[farmingPosition.setupIndex].endBlock ? 0 : farmingPosition.reward - reward;
farmingPosition.creationBlock = block.number;
} else {
currentBlock = currentBlock > _setups[farmingPosition.setupIndex].endBlock ? _setups[farmingPosition.setupIndex].endBlock : currentBlock;
_rewardPerTokenPerSetup[farmingPosition.setupIndex] += (((currentBlock - _setups[farmingPosition.setupIndex].lastUpdateBlock) * _setups[farmingPosition.setupIndex].rewardPerBlock) * 1e18) / _setups[farmingPosition.setupIndex].totalSupply;
reward = calculateFreeFarmingReward(positionId, false);
_rewardPerTokenPaid[positionId] = _rewardPerTokenPerSetup[farmingPosition.setupIndex];
farmingPosition.reward = 0;
_setups[farmingPosition.setupIndex].lastUpdateBlock = currentBlock;
}
if (reward > 0) {
if (_rewardTokenAddress != address(0)) {
_safeTransfer(_rewardTokenAddress, farmingPosition.uniqueOwner, reward);
} else {
(bool result,) = farmingPosition.uniqueOwner.call{value:reward}("");
require(result, "Invalid ETH transfer.");
}
_rewardPaid[farmingPosition.setupIndex] += reward;
}
if (_setups[farmingPosition.setupIndex].endBlock <= block.number) {
if (_setups[farmingPosition.setupIndex].active) {
_toggleSetup(farmingPosition.setupIndex);
}
if (!_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].free) {
_setupPositionsCount[farmingPosition.setupIndex] -= 1;
if (_setupPositionsCount[farmingPosition.setupIndex] == 0 && !_setups[farmingPosition.setupIndex].active) {
_giveBack(_rewardReceived[farmingPosition.setupIndex] - _rewardPaid[farmingPosition.setupIndex]);
delete _setups[farmingPosition.setupIndex];
}
delete _positions[positionId];
}
} else if (!_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].free) {
_partiallyRedeemed[positionId] += reward;
}
}
function withdrawLiquidity(uint256 positionId, uint256 objectId, bool unwrapPair, uint256 removedLiquidity) public {
FarmingPosition memory farmingPosition = _positions[positionId];
uint256 setupIndex = farmingPosition.setupIndex;
if (objectId != 0 && address(INativeV1(_farmTokenCollection).asInteroperable(objectId)) != address(0)) {
setupIndex = _objectIdSetup[objectId];
}
require((positionId != 0 && objectId == 0) || (objectId != 0 && positionId == 0 && _setups[setupIndex].objectId == objectId), "Invalid position");
require(
(
_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].free &&
farmingPosition.creationBlock != 0 &&
removedLiquidity <= farmingPosition.liquidityPoolTokenAmount &&
farmingPosition.uniqueOwner == msg.sender
) || (INativeV1(_farmTokenCollection).balanceOf(msg.sender, objectId) >= removedLiquidity && (_setups[setupIndex].endBlock <= block.number)), "Invalid withdraw");
if (positionId == 0) {
_burnFarmTokenAmount(objectId, removedLiquidity);
} else {
withdrawReward(positionId);
_setups[farmingPosition.setupIndex].totalSupply -= removedLiquidity;
}
_removeLiquidity(positionId, setupIndex, unwrapPair, removedLiquidity, false);
if (positionId == 0) {
_setupPositionsCount[setupIndex] -= removedLiquidity;
if (_setupPositionsCount[setupIndex] == 0 && !_setups[setupIndex].active) {
_giveBack(_rewardReceived[setupIndex] - _rewardPaid[setupIndex]);
delete _setups[setupIndex];
}
}
}
function unlock(uint256 positionId, bool unwrapPair) public payable byPositionOwner(positionId) {
FarmingPosition storage farmingPosition = _positions[positionId];
require(!_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].free && _setups[farmingPosition.setupIndex].endBlock > block.number, "Invalid unlock");
uint256 rewardToGiveBack = _partiallyRedeemed[positionId];
rewardToGiveBack += _setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].penaltyFee == 0 ? 0 : (farmingPosition.reward * ((_setupsInfo[_setups[farmingPosition.setupIndex].infoIndex].penaltyFee * 1e18) / ONE_HUNDRED) / 1e18);
if (rewardToGiveBack > 0) {
_safeTransferFrom(_rewardTokenAddress, msg.sender, address(this), rewardToGiveBack);
_giveBack(rewardToGiveBack);
}
_setups[farmingPosition.setupIndex].totalSupply -= farmingPosition.mainTokenAmount;
_burnFarmTokenAmount(_setups[farmingPosition.setupIndex].objectId, farmingPosition.liquidityPoolTokenAmount);
_removeLiquidity(positionId, farmingPosition.setupIndex, unwrapPair, farmingPosition.liquidityPoolTokenAmount, true);
_setupPositionsCount[farmingPosition.setupIndex] -= 1 + farmingPosition.liquidityPoolTokenAmount;
delete _positions[positionId];
}
function calculateLockedFarmingReward(uint256 setupIndex, uint256 mainTokenAmount, bool isPartial, uint256 positionId) public view returns(uint256 reward, uint256 relativeRewardPerBlock) {
if (isPartial) {
FarmingPosition memory farmingPosition = _positions[positionId];
uint256 currentBlock = block.number >= _setups[farmingPosition.setupIndex].endBlock ? _setups[farmingPosition.setupIndex].endBlock : block.number;
reward = ((currentBlock - farmingPosition.creationBlock) * farmingPosition.lockedRewardPerBlock);
} else {
FarmingSetup memory setup = _setups[setupIndex];
require(mainTokenAmount <= _setupsInfo[_setups[setupIndex].infoIndex].maxStakeable - setup.totalSupply, "Invalid liquidity");
uint256 remainingBlocks = block.number >= setup.endBlock ? 0 : setup.endBlock - block.number;
require(remainingBlocks > 0, "FarmingSetup ended");
require(setup.rewardPerBlock * remainingBlocks > 0, "No rewards");
relativeRewardPerBlock = (setup.rewardPerBlock * ((mainTokenAmount * 1e18) / _setupsInfo[_setups[setupIndex].infoIndex].maxStakeable)) / 1e18;
require(relativeRewardPerBlock > 0, "Invalid rpb");
reward = relativeRewardPerBlock * remainingBlocks;
}
}
function calculateFreeFarmingReward(uint256 positionId, bool isExt) public view returns(uint256 reward) {
FarmingPosition memory farmingPosition = _positions[positionId];
reward = ((_rewardPerTokenPerSetup[farmingPosition.setupIndex] - _rewardPerTokenPaid[positionId]) * farmingPosition.liquidityPoolTokenAmount) / 1e18;
if (isExt) {
uint256 currentBlock = block.number < _setups[farmingPosition.setupIndex].endBlock ? block.number : _setups[farmingPosition.setupIndex].endBlock;
uint256 lastUpdateBlock = _setups[farmingPosition.setupIndex].lastUpdateBlock < _setups[farmingPosition.setupIndex].startBlock ? _setups[farmingPosition.setupIndex].startBlock : _setups[farmingPosition.setupIndex].lastUpdateBlock;
uint256 rpt = (((currentBlock - lastUpdateBlock) * _setups[farmingPosition.setupIndex].rewardPerBlock) * 1e18) / _setups[farmingPosition.setupIndex].totalSupply;
reward += (rpt * farmingPosition.liquidityPoolTokenAmount) / 1e18;
}
reward += farmingPosition.reward;
}
function onERC1155BatchReceived(address, address, uint256[] memory, uint256[] memory, bytes memory) public view override returns(bytes4) {
require(_farmTokenCollection == msg.sender, "Invalid sender");
return this.onERC1155BatchReceived.selector;
}
function onERC1155Received(address, address, uint256, uint256, bytes memory) public view override returns(bytes4) {
require(_farmTokenCollection == msg.sender, "Invalid sender");
return this.onERC1155Received.selector;
}
function _setOrAddFarmingSetupInfo(FarmingSetupInfo memory info, bool add, bool disable, uint256 setupIndex) private {
FarmingSetupInfo memory farmingSetupInfo = info;
if(add || !disable) {
farmingSetupInfo.renewTimes = farmingSetupInfo.renewTimes + 1;
if(farmingSetupInfo.renewTimes == 0) {
farmingSetupInfo.renewTimes = farmingSetupInfo.renewTimes - 1;
}
}
if (add) {
require(
farmingSetupInfo.ammPlugin != address(0) &&
farmingSetupInfo.liquidityPoolTokenAddress != address(0) &&
farmingSetupInfo.originalRewardPerBlock > 0 &&
(farmingSetupInfo.free || farmingSetupInfo.maxStakeable > 0),
"Invalid setup configuration"
);
(,,address[] memory tokenAddresses) = IAMM(farmingSetupInfo.ammPlugin).byLiquidityPool(farmingSetupInfo.liquidityPoolTokenAddress);
farmingSetupInfo.ethereumAddress = address(0);
if (farmingSetupInfo.involvingETH) {
(farmingSetupInfo.ethereumAddress,,) = IAMM(farmingSetupInfo.ammPlugin).data();
}
bool mainTokenFound = false;
bool ethTokenFound = false;
for(uint256 z = 0; z < tokenAddresses.length; z++) {
if(tokenAddresses[z] == farmingSetupInfo.mainTokenAddress) {
mainTokenFound = true;
if(tokenAddresses[z] == farmingSetupInfo.ethereumAddress) {
ethTokenFound = true;
}
} else {
emit SetupToken(farmingSetupInfo.mainTokenAddress, tokenAddresses[z]);
if(tokenAddresses[z] == farmingSetupInfo.ethereumAddress) {
ethTokenFound = true;
}
}
}
require(mainTokenFound, "No main token");
require(!farmingSetupInfo.involvingETH || ethTokenFound, "No ETH token");
farmingSetupInfo.setupsCount = 0;
_setupsInfo[_farmingSetupsInfoCount] = farmingSetupInfo;
_setups[_farmingSetupsCount] = FarmingSetup(_farmingSetupsInfoCount, false, 0, 0, 0, 0, farmingSetupInfo.originalRewardPerBlock, 0);
_setupsInfo[_farmingSetupsInfoCount].lastSetupIndex = _farmingSetupsCount;
_farmingSetupsInfoCount += 1;
_farmingSetupsCount += 1;
return;
}
FarmingSetup storage setup = _setups[setupIndex];
farmingSetupInfo = _setupsInfo[_setups[setupIndex].infoIndex];
if(disable) {
require(setup.active, "Not possible");
_toggleSetup(setupIndex);
return;
}
info.renewTimes -= 1;
if (setup.active && _setupsInfo[_setups[setupIndex].infoIndex].free) {
setup = _setups[setupIndex];
if(block.number < setup.endBlock) {
uint256 difference = info.originalRewardPerBlock < farmingSetupInfo.originalRewardPerBlock ? farmingSetupInfo.originalRewardPerBlock - info.originalRewardPerBlock : info.originalRewardPerBlock - farmingSetupInfo.originalRewardPerBlock;
uint256 duration = setup.endBlock - block.number;
uint256 amount = difference * duration;
if (amount > 0) {
if (info.originalRewardPerBlock > farmingSetupInfo.originalRewardPerBlock) {
require(_ensureTransfer(amount), "Insufficient reward in extension.");
_rewardReceived[setupIndex] += amount;
}
_updateFreeSetup(setupIndex, 0, 0, false);
setup.rewardPerBlock = info.originalRewardPerBlock;
}
}
_setupsInfo[_setups[setupIndex].infoIndex].originalRewardPerBlock = info.originalRewardPerBlock;
}
if(_setupsInfo[_setups[setupIndex].infoIndex].renewTimes > 0) {
_setupsInfo[_setups[setupIndex].infoIndex].renewTimes = info.renewTimes;
}
}
function _transferToMeAndCheckAllowance(FarmingSetup memory setup, FarmingPositionRequest memory request) private returns(IAMM amm, uint256 liquidityPoolAmount, uint256 mainTokenAmount) {
require(request.amount > 0, "No amount");
amm = IAMM(_setupsInfo[setup.infoIndex].ammPlugin);
liquidityPoolAmount = request.amountIsLiquidityPool ? request.amount : 0;
mainTokenAmount = request.amountIsLiquidityPool ? 0 : request.amount;
address[] memory tokens;
uint256[] memory tokenAmounts;
if(request.amountIsLiquidityPool) {
_safeTransferFrom(_setupsInfo[setup.infoIndex].liquidityPoolTokenAddress, msg.sender, address(this), liquidityPoolAmount);
(tokenAmounts, tokens) = amm.byLiquidityPoolAmount(_setupsInfo[setup.infoIndex].liquidityPoolTokenAddress, liquidityPoolAmount);
} else {
(liquidityPoolAmount, tokenAmounts, tokens) = amm.byTokenAmount(_setupsInfo[setup.infoIndex].liquidityPoolTokenAddress, _setupsInfo[setup.infoIndex].mainTokenAddress, mainTokenAmount);
}
for(uint256 i = 0; i < tokens.length; i++) {
if(tokens[i] == _setupsInfo[setup.infoIndex].mainTokenAddress) {
mainTokenAmount = tokenAmounts[i];
require(mainTokenAmount >= _setupsInfo[setup.infoIndex].minStakeable, "Invalid liquidity.");
if(request.amountIsLiquidityPool) {
break;
}
}
if(request.amountIsLiquidityPool) {
continue;
}
if(_setupsInfo[setup.infoIndex].involvingETH && _setupsInfo[setup.infoIndex].ethereumAddress == tokens[i]) {
require(msg.value == tokenAmounts[i], "Incorrect eth value");
} else {
_safeTransferFrom(tokens[i], msg.sender, address(this), tokenAmounts[i]);
_safeApprove(tokens[i], _setupsInfo[setup.infoIndex].ammPlugin, tokenAmounts[i]);
}
}
}
function _addLiquidity(uint256 setupIndex, FarmingPositionRequest memory request) private returns(LiquidityPoolData memory liquidityPoolData, uint256 tokenAmount) {
(IAMM amm, uint256 liquidityPoolAmount, uint256 mainTokenAmount) = _transferToMeAndCheckAllowance(_setups[setupIndex], request);
liquidityPoolData = LiquidityPoolData(
_setupsInfo[_setups[setupIndex].infoIndex].liquidityPoolTokenAddress,
request.amountIsLiquidityPool ? liquidityPoolAmount : mainTokenAmount,
_setupsInfo[_setups[setupIndex].infoIndex].mainTokenAddress,
request.amountIsLiquidityPool,
_setupsInfo[_setups[setupIndex].infoIndex].involvingETH,
address(this)
);
tokenAmount = mainTokenAmount;
if (liquidityPoolData.amountIsLiquidityPool || !_setupsInfo[_setups[setupIndex].infoIndex].involvingETH) {
require(msg.value == 0, "ETH not involved");
}
if (liquidityPoolData.amountIsLiquidityPool) {
return(liquidityPoolData, tokenAmount);
}
if(liquidityPoolData.involvingETH) {
(liquidityPoolData.amount,,) = amm.addLiquidity{value : msg.value}(liquidityPoolData);
} else {
(liquidityPoolData.amount,,) = amm.addLiquidity(liquidityPoolData);
}
}
function _removeLiquidity(uint256 positionId, uint256 setupIndex, bool unwrapPair, uint256 removedLiquidity, bool isUnlock) private {
FarmingSetupInfo memory setupInfo = _setupsInfo[_setups[setupIndex].infoIndex];
LiquidityPoolData memory lpData = LiquidityPoolData(
setupInfo.liquidityPoolTokenAddress,
removedLiquidity,
setupInfo.mainTokenAddress,
true,
setupInfo.involvingETH,
msg.sender
);
FarmingPosition storage farmingPosition = _positions[positionId];
uint256 remainingLiquidity;
if (setupInfo.free && farmingPosition.creationBlock != 0 && positionId != 0) {
remainingLiquidity = farmingPosition.liquidityPoolTokenAmount - removedLiquidity;
}
(uint256 exitFeePercentage, address exitFeeWallet) = IFarmFactory(_factory).feePercentageInfo();
if (exitFeePercentage > 0) {
uint256 fee = (lpData.amount * ((exitFeePercentage * 1e18) / ONE_HUNDRED)) / 1e18;
_safeTransfer(setupInfo.liquidityPoolTokenAddress, exitFeeWallet, fee);
lpData.amount = lpData.amount - fee;
}
if (unwrapPair) {
_safeApprove(lpData.liquidityPoolAddress, setupInfo.ammPlugin, lpData.amount);
IAMM(setupInfo.ammPlugin).removeLiquidity(lpData);
} else {
_safeTransfer(lpData.liquidityPoolAddress, lpData.receiver, lpData.amount);
}
if (!setupInfo.free && _setups[setupIndex].active && !isUnlock) {
_toggleSetup(setupIndex);
} else if (setupInfo.free && positionId != 0) {
if (_setups[farmingPosition.setupIndex].active && _setups[farmingPosition.setupIndex].endBlock <= block.number) {
_toggleSetup(farmingPosition.setupIndex);
}
if (remainingLiquidity == 0) {
_setupPositionsCount[farmingPosition.setupIndex] -= 1;
if (_setupPositionsCount[farmingPosition.setupIndex] == 0 && !_setups[farmingPosition.setupIndex].active) {
_giveBack(_rewardReceived[farmingPosition.setupIndex] - _rewardPaid[farmingPosition.setupIndex]);
delete _setups[farmingPosition.setupIndex];
}
delete _positions[positionId];
} else {
farmingPosition.liquidityPoolTokenAmount = remainingLiquidity;
}
}
}
function _updateFreeSetup(uint256 setupIndex, uint256 amount, uint256 positionId, bool fromExit) private {
uint256 currentBlock = block.number < _setups[setupIndex].endBlock ? block.number : _setups[setupIndex].endBlock;
if (_setups[setupIndex].totalSupply != 0) {
uint256 lastUpdateBlock = _setups[setupIndex].lastUpdateBlock < _setups[setupIndex].startBlock ? _setups[setupIndex].startBlock : _setups[setupIndex].lastUpdateBlock;
_rewardPerTokenPerSetup[setupIndex] += (((currentBlock - lastUpdateBlock) * _setups[setupIndex].rewardPerBlock) * 1e18) / _setups[setupIndex].totalSupply;
}
_setups[setupIndex].lastUpdateBlock = currentBlock;
if (positionId != 0) {
_rewardPerTokenPaid[positionId] = _rewardPerTokenPerSetup[setupIndex];
}
if (amount > 0) {
fromExit ? _setups[setupIndex].totalSupply -= amount : _setups[setupIndex].totalSupply += amount;
}
}
function _toggleSetup(uint256 setupIndex) private {
FarmingSetup storage setup = _setups[setupIndex];
if (setup.active && block.number >= setup.endBlock && _setupsInfo[setup.infoIndex].renewTimes == 0) {
setup.active = false;
return;
} else if (block.number >= setup.startBlock && block.number < setup.endBlock && setup.active) {
setup.active = false;
_setupsInfo[setup.infoIndex].renewTimes = 0;
uint256 amount = (setup.endBlock - block.number) * setup.rewardPerBlock;
setup.endBlock = block.number;
if (_setupsInfo[setup.infoIndex].free) {
_updateFreeSetup(setupIndex, 0, 0, false);
}
_rewardReceived[setupIndex] -= amount;
_giveBack(amount);
return;
}
bool wasActive = setup.active;
setup.active = _ensureTransfer(setup.rewardPerBlock * _setupsInfo[setup.infoIndex].blockDuration);
if (setup.active && wasActive) {
_rewardReceived[_farmingSetupsCount] = setup.rewardPerBlock * _setupsInfo[setup.infoIndex].blockDuration;
_setups[_farmingSetupsCount] = abi.decode(abi.encode(setup), (FarmingSetup));
_setups[setupIndex].active = false;
_setupsInfo[setup.infoIndex].renewTimes -= 1;
_setupsInfo[setup.infoIndex].setupsCount += 1;
_setupsInfo[setup.infoIndex].lastSetupIndex = _farmingSetupsCount;
_setups[_farmingSetupsCount].startBlock = block.number;
_setups[_farmingSetupsCount].endBlock = block.number + _setupsInfo[_setups[_farmingSetupsCount].infoIndex].blockDuration;
_setups[_farmingSetupsCount].objectId = 0;
_setups[_farmingSetupsCount].totalSupply = 0;
_farmingSetupsCount += 1;
} else if (setup.active && !wasActive) {
_rewardReceived[setupIndex] = setup.rewardPerBlock * _setupsInfo[_setups[setupIndex].infoIndex].blockDuration;
_setups[setupIndex].startBlock = block.number;
_setups[setupIndex].endBlock = block.number + _setupsInfo[_setups[setupIndex].infoIndex].blockDuration;
_setups[setupIndex].totalSupply = 0;
_setupsInfo[_setups[setupIndex].infoIndex].renewTimes -= 1;
} else {
_setupsInfo[_setups[setupIndex].infoIndex].renewTimes = 0;
}
}
function _mintFarmTokenAmount(address uniqueOwner, uint256 amount, uint256 setupIndex) private returns(uint256 objectId) {
if (_setups[setupIndex].objectId == 0) {
(objectId,) = INativeV1(_farmTokenCollection).mint(amount, string(abi.encodePacked("Farming LP ", _toString(_setupsInfo[_setups[setupIndex].infoIndex].liquidityPoolTokenAddress))), "fLP", IFarmFactory(_factory).getFarmTokenURI(), true);
emit FarmToken(objectId, _setupsInfo[_setups[setupIndex].infoIndex].liquidityPoolTokenAddress, setupIndex, _setups[setupIndex].endBlock);
_objectIdSetup[objectId] = setupIndex;
_setups[setupIndex].objectId = objectId;
} else {
INativeV1(_farmTokenCollection).mint(_setups[setupIndex].objectId, amount);
}
INativeV1(_farmTokenCollection).safeTransferFrom(address(this), uniqueOwner, _setups[setupIndex].objectId, amount, "");
}
function _burnFarmTokenAmount(uint256 objectId, uint256 amount) private {
INativeV1 tokenCollection = INativeV1(_farmTokenCollection);
tokenCollection.safeTransferFrom(msg.sender, address(this), objectId, amount, "");
tokenCollection.burn(objectId, amount);
}
function _safeApprove(address erc20TokenAddress, address to, uint256 value) internal virtual {
bytes memory returnData = _call(erc20TokenAddress, abi.encodeWithSelector(IERC20(erc20TokenAddress).approve.selector, to, value));
require(returnData.length == 0 || abi.decode(returnData, (bool)), 'APPROVE_FAILED');
}
function _safeTransfer(address erc20TokenAddress, address to, uint256 value) internal virtual {
bytes memory returnData = _call(erc20TokenAddress, abi.encodeWithSelector(IERC20(erc20TokenAddress).transfer.selector, to, value));
require(returnData.length == 0 || abi.decode(returnData, (bool)), 'TRANSFER_FAILED');
}
function _safeTransferFrom(address erc20TokenAddress, address from, address to, uint256 value) private {
bytes memory returnData = _call(erc20TokenAddress, abi.encodeWithSelector(IERC20(erc20TokenAddress).transferFrom.selector, from, to, value));
require(returnData.length == 0 || abi.decode(returnData, (bool)), 'TRANSFERFROM_FAILED');
}
function _call(address location, bytes memory payload) private returns(bytes memory returnData) {
assembly {
let result := call(gas(), location, 0, add(payload, 0x20), mload(payload), 0, 0)
let size := returndatasize()
returnData := mload(0x40)
mstore(returnData, size)
let returnDataPayloadStart := add(returnData, 0x20)
returndatacopy(returnDataPayloadStart, 0, size)
mstore(0x40, add(returnDataPayloadStart, size))
switch result case 0 {revert(returnDataPayloadStart, size)}
}
}
function _toString(address _addr) internal pure returns(string memory) {
bytes32 value = bytes32(uint256(_addr));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(42);
str[0] = '0';
str[1] = 'x';
for (uint i = 0; i < 20; i++) {
str[2+i*2] = alphabet[uint(uint8(value[i + 12] >> 4))];
str[3+i*2] = alphabet[uint(uint8(value[i + 12] & 0x0f))];
}
return string(str);
}
function _giveBack(uint256 amount) private {
if(amount == 0) {
return;
}
if (_rewardTokenAddress == address(0)) {
IFarmExtension(_extension).backToYou{value : amount}(amount);
} else {
_safeApprove(_rewardTokenAddress, _extension, amount);
IFarmExtension(_extension).backToYou(amount);
}
}
function _ensureTransfer(uint256 amount) private returns(bool) {
uint256 initialBalance = _rewardTokenAddress == address(0) ? address(this).balance : IERC20(_rewardTokenAddress).balanceOf(address(this));
uint256 expectedBalance = initialBalance + amount;
try IFarmExtension(_extension).transferTo(amount) {} catch {}
uint256 actualBalance = _rewardTokenAddress == address(0) ? address(this).balance : IERC20(_rewardTokenAddress).balanceOf(address(this));
if(actualBalance == expectedBalance) {
return true;
}
_giveBack(actualBalance - initialBalance);
return false;
}
}