Contract Name:
StakingFacet
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
import "../libraries/AppStorage.sol";
import "../libraries/LibDiamond.sol";
import "../libraries/LibERC20.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC1155TokenReceiver.sol";
import "../interfaces/IUniswapV2Pair.sol";
contract StakingFacet {
AppStorage internal s;
bytes4 internal constant ERC1155_BATCH_ACCEPTED = 0xbc197c81; // Return value from `onERC1155BatchReceived` call if a contract accepts receipt (i.e `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`).
event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
function frens(address _account) public view returns (uint256 frens_) {
Account memory account = s.accounts[_account];
// this cannot underflow or overflow
uint256 timePeriod = block.timestamp - account.lastUpdate;
frens_ = account.frens;
// 86400 the number of seconds in 1 day
// 100 frens are generated for each LP token over 24 hours
frens_ += ((uint256(account.uniV2PoolTokens) * 100) * timePeriod) / 86400;
// 1 fren is generated for each GHST over 24 hours
frens_ += (account.ghst * timePeriod) / 86400;
}
function updateFrens() internal {
Account storage account = s.accounts[msg.sender];
account.frens = uint104(frens(msg.sender));
account.lastUpdate = uint40(block.timestamp);
}
function stakeGhst(uint256 _ghstValue) external {
updateFrens();
s.accounts[msg.sender].ghst += uint96(_ghstValue);
LibERC20.transferFrom(s.ghstContract, msg.sender, address(this), _ghstValue);
}
function stakeUniV2PoolTokens(uint256 _uniV2PoolTokens) external {
updateFrens();
s.accounts[msg.sender].uniV2PoolTokens += uint96(_uniV2PoolTokens);
LibERC20.transferFrom(s.uniV2PoolContract, msg.sender, address(this), _uniV2PoolTokens);
}
function staked(address _account) external view returns (uint256 ghst_, uint256 uniV2PoolTokens_) {
ghst_ = s.accounts[_account].ghst;
uniV2PoolTokens_ = s.accounts[_account].uniV2PoolTokens;
}
function withdrawGhstStake(uint256 _ghstValue) external {
updateFrens();
uint256 bal = s.accounts[msg.sender].ghst;
require(bal >= _ghstValue, "Staking: Can't withdraw more than staked");
s.accounts[msg.sender].ghst = uint96(bal - _ghstValue);
LibERC20.transfer(s.ghstContract, msg.sender, _ghstValue);
}
function withdrawUniV2PoolStake(uint256 _uniV2PoolTokens) external {
updateFrens();
uint256 bal = s.accounts[msg.sender].uniV2PoolTokens;
require(bal >= _uniV2PoolTokens, "Staking: Can't withdraw more than staked");
s.accounts[msg.sender].uniV2PoolTokens = uint96(bal - _uniV2PoolTokens);
LibERC20.transfer(s.uniV2PoolContract, msg.sender, _uniV2PoolTokens);
}
function withdrawGhstStake() external {
updateFrens();
uint256 bal = s.accounts[msg.sender].ghst;
s.accounts[msg.sender].ghst = uint96(0);
LibERC20.transfer(s.ghstContract, msg.sender, bal);
}
function withdrawUniV2PoolStake() external {
updateFrens();
uint256 bal = s.accounts[msg.sender].uniV2PoolTokens;
s.accounts[msg.sender].uniV2PoolTokens = uint96(0);
LibERC20.transfer(s.uniV2PoolContract, msg.sender, bal);
}
function claimTickets(uint256[] calldata _ids, uint256[] calldata _values) external {
require(_ids.length == _values.length, "Staking: _ids not the same length as _values");
updateFrens();
uint256 frensBal = s.accounts[msg.sender].frens;
for (uint256 i; i < _ids.length; i++) {
uint256 id = _ids[i];
uint256 value = _values[i];
require(id < 6, "Staking: Ticket not found");
uint256 cost = ticketCost(id) * value;
require(frensBal >= cost, "Staking: Not enough frens points");
frensBal -= cost;
s.tickets[id].accountBalances[msg.sender] += value;
s.tickets[id].totalSupply += uint96(value);
}
s.accounts[msg.sender].frens = uint104(frensBal);
emit TransferBatch(msg.sender, address(0), msg.sender, _ids, _values);
uint256 size;
address to = msg.sender;
assembly {
size := extcodesize(to)
}
if (size > 0) {
require(
ERC1155_BATCH_ACCEPTED ==
IERC1155TokenReceiver(msg.sender).onERC1155BatchReceived(msg.sender, address(0), _ids, _values, new bytes(0)),
"Staking: Ticket transfer rejected/failed"
);
}
}
function ticketCost(uint256 _id) public pure returns (uint256 _frensCost) {
if (_id == 0) {
_frensCost = 50e18;
} else if (_id == 1) {
_frensCost = 250e18;
} else if (_id == 2) {
_frensCost = 500e18;
} else if (_id == 3) {
_frensCost = 2_500e18;
} else if (_id == 4) {
_frensCost = 10_000e18;
} else if (_id == 5) {
_frensCost = 50_000e18;
} else {
revert("Staking: _id does not exist");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
struct Account {
uint96 ghst;
uint96 uniV2PoolTokens;
uint40 lastUpdate;
uint104 frens;
}
struct Ticket {
// user address => balance
mapping(address => uint256) accountBalances;
uint96 totalSupply;
}
struct AppStorage {
mapping(address => mapping(address => bool)) approved;
mapping(address => Account) accounts;
mapping(uint256 => Ticket) tickets;
address ghstContract;
address uniV2PoolContract;
string ticketsBaseUri;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
pragma experimental ABIEncoderV2;
/******************************************************************************\
* Author: Nick Mudge
*
* Implementation of Diamond facet.
* Uses the diamond-2 version 1.3.4 implementation:
* https://github.com/mudgen/diamond-2
*
* This is gas optimized by reducing storage reads and storage writes.
* This code is as complex as it is to reduce gas costs.
/******************************************************************************/
import "../interfaces/IDiamondCut.sol";
library LibDiamond {
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
struct DiamondStorage {
// maps function selectors to the facets that execute the functions.
// and maps the selectors to their position in the selectorSlots array.
// func selector => address facet, selector position
mapping(bytes4 => bytes32) facets;
// array of slots of function selectors.
// each slot holds 8 function selectors.
mapping(uint256 => bytes32) selectorSlots;
// The number of function selectors in selectorSlots
uint16 selectorCount;
// owner of the contract
// Used to query if a contract implements an interface.
// Used to implement ERC-165.
mapping(bytes4 => bool) supportedInterfaces;
// owner of the contract
address contractOwner;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
}
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
address previousOwner = ds.contractOwner;
ds.contractOwner = _newOwner;
emit OwnershipTransferred(previousOwner, _newOwner);
}
function contractOwner() internal view returns (address contractOwner_) {
contractOwner_ = diamondStorage().contractOwner;
}
function enforceIsContractOwner() internal view {
require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner");
}
modifier onlyOwner {
require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner");
_;
}
event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
bytes32 constant CLEAR_ADDRESS_MASK = bytes32(uint256(0xffffffffffffffffffffffff));
bytes32 constant CLEAR_SELECTOR_MASK = bytes32(uint256(0xffffffff << 224));
// Internal function version of diamondCut
// This code is almost the same as the external diamondCut,
// except it is using 'Facet[] memory _diamondCut' instead of
// 'Facet[] calldata _diamondCut'.
// The code is duplicated to prevent copying calldata to memory which
// causes an error for a two dimensional array.
function diamondCut(
IDiamondCut.FacetCut[] memory _diamondCut,
address _init,
bytes memory _calldata
) internal {
DiamondStorage storage ds = diamondStorage();
uint256 originalSelectorCount = ds.selectorCount;
uint256 selectorCount = originalSelectorCount;
bytes32 selectorSlot;
// Check if last selector slot is not full
if (selectorCount % 8 > 0) {
// get last selectorSlot
selectorSlot = ds.selectorSlots[selectorCount / 8];
}
// loop through diamond cut
for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) {
(selectorCount, selectorSlot) = addReplaceRemoveFacetSelectors(
selectorCount,
selectorSlot,
_diamondCut[facetIndex].facetAddress,
_diamondCut[facetIndex].action,
_diamondCut[facetIndex].functionSelectors
);
}
if (selectorCount != originalSelectorCount) {
ds.selectorCount = uint16(selectorCount);
}
// If last selector slot is not full
if (selectorCount % 8 > 0) {
ds.selectorSlots[selectorCount / 8] = selectorSlot;
}
emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addReplaceRemoveFacetSelectors(
uint256 _selectorCount,
bytes32 _selectorSlot,
address _newFacetAddress,
IDiamondCut.FacetCutAction _action,
bytes4[] memory _selectors
) internal returns (uint256, bytes32) {
DiamondStorage storage ds = diamondStorage();
require(_selectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
if (_action == IDiamondCut.FacetCutAction.Add) {
require(_newFacetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
enforceHasContractCode(_newFacetAddress, "LibDiamondCut: Add facet has no code");
for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) {
bytes4 selector = _selectors[selectorIndex];
bytes32 oldFacet = ds.facets[selector];
require(address(bytes20(oldFacet)) == address(0), "LibDiamondCut: Can't add function that already exists");
// add facet for selector
ds.facets[selector] = bytes20(_newFacetAddress) | bytes32(_selectorCount);
uint256 selectorInSlotPosition = (_selectorCount % 8) * 32;
// clear selector position in slot and add selector
_selectorSlot = (_selectorSlot & ~(CLEAR_SELECTOR_MASK >> selectorInSlotPosition)) | (bytes32(selector) >> selectorInSlotPosition);
// if slot is full then write it to storage
if (selectorInSlotPosition == 224) {
ds.selectorSlots[_selectorCount / 8] = _selectorSlot;
_selectorSlot = 0;
}
_selectorCount++;
}
} else if (_action == IDiamondCut.FacetCutAction.Replace) {
require(_newFacetAddress != address(0), "LibDiamondCut: Replace facet can't be address(0)");
enforceHasContractCode(_newFacetAddress, "LibDiamondCut: Replace facet has no code");
for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) {
bytes4 selector = _selectors[selectorIndex];
bytes32 oldFacet = ds.facets[selector];
address oldFacetAddress = address(bytes20(oldFacet));
// only useful if immutable functions exist
require(oldFacetAddress != address(this), "LibDiamondCut: Can't replace immutable function");
require(oldFacetAddress != _newFacetAddress, "LibDiamondCut: Can't replace function with same function");
require(oldFacetAddress != address(0), "LibDiamondCut: Can't replace function that doesn't exist");
// replace old facet address
ds.facets[selector] = (oldFacet & CLEAR_ADDRESS_MASK) | bytes20(_newFacetAddress);
}
} else if (_action == IDiamondCut.FacetCutAction.Remove) {
require(_newFacetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)");
uint256 selectorSlotCount = _selectorCount / 8;
uint256 selectorInSlotIndex = (_selectorCount % 8) - 1;
for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) {
if (_selectorSlot == 0) {
// get last selectorSlot
selectorSlotCount--;
_selectorSlot = ds.selectorSlots[selectorSlotCount];
selectorInSlotIndex = 7;
}
bytes4 lastSelector;
uint256 oldSelectorsSlotCount;
uint256 oldSelectorInSlotPosition;
// adding a block here prevents stack too deep error
{
bytes4 selector = _selectors[selectorIndex];
bytes32 oldFacet = ds.facets[selector];
require(address(bytes20(oldFacet)) != address(0), "LibDiamondCut: Can't remove function that doesn't exist");
// only useful if immutable functions exist
require(address(bytes20(oldFacet)) != address(this), "LibDiamondCut: Can't remove immutable function");
// replace selector with last selector in ds.facets
// gets the last selector
lastSelector = bytes4(_selectorSlot << (selectorInSlotIndex * 32));
if (lastSelector != selector) {
// update last selector slot position info
ds.facets[lastSelector] = (oldFacet & CLEAR_ADDRESS_MASK) | bytes20(ds.facets[lastSelector]);
}
delete ds.facets[selector];
uint256 oldSelectorCount = uint16(uint256(oldFacet));
oldSelectorsSlotCount = oldSelectorCount / 8;
oldSelectorInSlotPosition = (oldSelectorCount % 8) * 32;
}
if (oldSelectorsSlotCount != selectorSlotCount) {
bytes32 oldSelectorSlot = ds.selectorSlots[oldSelectorsSlotCount];
// clears the selector we are deleting and puts the last selector in its place.
oldSelectorSlot =
(oldSelectorSlot & ~(CLEAR_SELECTOR_MASK >> oldSelectorInSlotPosition)) |
(bytes32(lastSelector) >> oldSelectorInSlotPosition);
// update storage with the modified slot
ds.selectorSlots[oldSelectorsSlotCount] = oldSelectorSlot;
} else {
// clears the selector we are deleting and puts the last selector in its place.
_selectorSlot =
(_selectorSlot & ~(CLEAR_SELECTOR_MASK >> oldSelectorInSlotPosition)) |
(bytes32(lastSelector) >> oldSelectorInSlotPosition);
}
if (selectorInSlotIndex == 0) {
delete ds.selectorSlots[selectorSlotCount];
_selectorSlot = 0;
}
selectorInSlotIndex--;
}
_selectorCount = selectorSlotCount * 8 + selectorInSlotIndex + 1;
} else {
revert("LibDiamondCut: Incorrect FacetCutAction");
}
return (_selectorCount, _selectorSlot);
}
function initializeDiamondCut(address _init, bytes memory _calldata) internal {
if (_init == address(0)) {
require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty");
} else {
require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)");
if (_init != address(this)) {
enforceHasContractCode(_init, "LibDiamondCut: _init address has no code");
}
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length > 0) {
// bubble up the error
revert(string(error));
} else {
revert("LibDiamondCut: _init function reverted");
}
}
}
}
function enforceHasContractCode(address _contract, string memory _errorMessage) internal view {
uint256 contractSize;
assembly {
contractSize := extcodesize(_contract)
}
require(contractSize > 0, _errorMessage);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
pragma experimental ABIEncoderV2;
/******************************************************************************\
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen)
/******************************************************************************/
interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
/******************************************************************************\
* Author: Nick Mudge
*
/******************************************************************************/
import "../interfaces/IERC20.sol";
library LibERC20 {
function transferFrom(
address _token,
address _from,
address _to,
uint256 _value
) internal {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: Address has no code");
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _value));
handleReturn(success, result);
}
function transfer(
address _token,
address _to,
uint256 _value
) internal {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: Address has no code");
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _value));
handleReturn(success, result);
}
function handleReturn(bool _success, bytes memory _result) internal pure {
if (_success) {
if (_result.length > 0) {
require(abi.decode(_result, (bool)), "LibERC20: contract call returned false");
}
} else {
if (_result.length > 0) {
// bubble up any reason for revert
revert(string(_result));
} else {
revert("LibERC20: contract call reverted");
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address _owner) external view returns (uint256 balance);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.3;
/**
Note: The ERC-165 identifier for this interface is 0x4e2312e0.
*/
interface IERC1155TokenReceiver {
/**
@notice Handle the receipt of a single ERC1155 token type.
@dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated.
This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer.
This function MUST revert if it rejects the transfer.
Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.
@param _operator The address which initiated the transfer (i.e. msg.sender)
@param _from The address which previously owned the token
@param _id The ID of the token being transferred
@param _value The amount of tokens being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
*/
function onERC1155Received(
address _operator,
address _from,
uint256 _id,
uint256 _value,
bytes calldata _data
) external returns (bytes4);
/**
@notice Handle the receipt of multiple ERC1155 token types.
@dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated.
This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s).
This function MUST revert if it rejects the transfer(s).
Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.
@param _operator The address which initiated the batch transfer (i.e. msg.sender)
@param _from The address which previously owned the token
@param _ids An array containing ids of each token being transferred (order and length must match _values array)
@param _values An array containing amounts of each token being transferred (order and length must match _ids array)
@param _data Additional data with no specified format
@return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
*/
function onERC1155BatchReceived(
address _operator,
address _from,
uint256[] calldata _ids,
uint256[] calldata _values,
bytes calldata _data
) external returns (bytes4);
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.3;
// Taken from https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint256);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint256);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);
function price0CumulativeLast() external view returns (uint256);
function price1CumulativeLast() external view returns (uint256);
function kLast() external view returns (uint256);
function mint(address to) external returns (uint256 liquidity);
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}