mirror of https://github.com/logos-co/staking.git
Merge pull request #1 from logos-co/research
Merge research into develop
This commit is contained in:
commit
24e59ff4a8
|
@ -0,0 +1,13 @@
|
|||
node_modules
|
||||
.env
|
||||
coverage
|
||||
coverage.json
|
||||
typechain
|
||||
typechain-types
|
||||
|
||||
# Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
|
||||
gmx-contracts
|
||||
node_modules
|
|
@ -0,0 +1,224 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "./StakeVault.sol";
|
||||
|
||||
contract StakeManager is Ownable {
|
||||
|
||||
struct Account {
|
||||
uint256 lockUntil;
|
||||
uint256 balance;
|
||||
uint256 multiplier;
|
||||
uint256 lastMint;
|
||||
uint256 epoch;
|
||||
address rewardAddress;
|
||||
}
|
||||
|
||||
struct Epoch {
|
||||
uint256 startTime;
|
||||
uint256 epochReward;
|
||||
uint256 totalSupply;
|
||||
}
|
||||
|
||||
uint256 public constant EPOCH_SIZE = 1 weeks;
|
||||
uint256 public constant MP_APY = 1;
|
||||
uint256 public constant STAKE_APY = 1;
|
||||
uint256 public constant MAX_BOOST = 1;
|
||||
uint256 public constant MAX_MP = 1;
|
||||
|
||||
mapping (address => Account) accounts;
|
||||
mapping (uint256 => Epoch) epochs;
|
||||
mapping (bytes32 => bool) isVault;
|
||||
|
||||
|
||||
uint256 public currentEpoch;
|
||||
uint256 public pendingReward;
|
||||
uint256 public multiplierSupply;
|
||||
uint256 public stakeSupply;
|
||||
StakeManager public migration;
|
||||
StakeManager public immutable oldManager;
|
||||
ERC20 public immutable stakedToken;
|
||||
modifier onlyVault {
|
||||
require(isVault[msg.sender.codehash], "Not a vault");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(ERC20 _stakedToken, StakeManager _oldManager) Ownable() {
|
||||
epochs[0].startTime = block.timestamp;
|
||||
oldManager = _oldManager;
|
||||
stakedToken = _stakedToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases balance of msg.sender;
|
||||
* @param _amount Amount of balance to be decreased.
|
||||
* @param _time Seconds from block.timestamp to lock balance.
|
||||
*/
|
||||
function stake(uint256 _amount, uint256 _time) external onlyVault {
|
||||
Account storage account = accounts[msg.sender];
|
||||
processAccount(account, currentEpoch);
|
||||
account.balance += _amount;
|
||||
account.rewardAddress = StakeVault(msg.sender).owner();
|
||||
mintIntialMultiplier(account, _time);
|
||||
stakeSupply += _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases balance of msg.sender;
|
||||
* @param _amount Amount of balance to be decreased
|
||||
*/
|
||||
function unstake(uint256 _amount) external onlyVault {
|
||||
Account storage account = accounts[msg.sender];
|
||||
require(account.lockUntil <= block.timestamp, "Funds are locked");
|
||||
processAccount(account, currentEpoch);
|
||||
uint256 reducedMultiplier = (_amount * account.multiplier) / account.balance;
|
||||
account.multiplier -= reducedMultiplier;
|
||||
account.balance -= _amount;
|
||||
multiplierSupply -= reducedMultiplier;
|
||||
stakeSupply -= _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Locks entire balance for more amount of time.
|
||||
* @param _time amount of time to lock from now.
|
||||
*/
|
||||
function lock(uint256 _time) external onlyVault {
|
||||
Account storage account = accounts[msg.sender];
|
||||
processAccount(account, currentEpoch);
|
||||
require(block.timestamp + _time > account.lockUntil, "Cannot decrease lock time");
|
||||
mintIntialMultiplier(account, _time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice leave without processing account
|
||||
*/
|
||||
function leave() external onlyVault {
|
||||
require(address(migration) != address(0), "Leave only during migration");
|
||||
Account memory account = accounts[msg.sender];
|
||||
delete accounts[msg.sender];
|
||||
multiplierSupply -= account.multiplier;
|
||||
stakeSupply -= account.balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Release rewards for current epoch and increase epoch.
|
||||
*/
|
||||
function executeEpoch() external {
|
||||
processEpoch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Execute rewards for account until limit has reached
|
||||
* @param _vault Referred account
|
||||
* @param _limitEpoch Until what epoch it should be executed
|
||||
*/
|
||||
function executeAccount(address _vault, uint256 _limitEpoch) external {
|
||||
processAccount(accounts[_vault], _limitEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Enables a contract class to interact with staking functions
|
||||
* @param _codehash bytecode hash of contract
|
||||
*/
|
||||
function setVault(bytes32 _codehash) external onlyOwner {
|
||||
isVault[_codehash] = true;
|
||||
}
|
||||
/**
|
||||
* @notice Migrate account to new manager.
|
||||
*/
|
||||
function migrate() external onlyVault returns (StakeManager newManager) {
|
||||
require(address(migration) != address(0), "Migration not available");
|
||||
Account storage account = accounts[msg.sender];
|
||||
stakedToken.approve(address(migration), account.balance);
|
||||
migration.migrate(msg.sender, account);
|
||||
delete accounts[msg.sender];
|
||||
return migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Only callable from old manager.
|
||||
* @notice Migrate account from old manager
|
||||
* @param _vault Account address
|
||||
* @param _account Account data
|
||||
*/
|
||||
function migrate(address _vault, Account memory _account) external {
|
||||
require(msg.sender == address(oldManager), "Unauthorized");
|
||||
stakedToken.transferFrom(address(oldManager), address(this), _account.balance);
|
||||
accounts[_vault] = _account;
|
||||
}
|
||||
|
||||
function calcMaxMultiplierIncrease(uint256 _increasedMultiplier, uint256 _currentMp) private pure returns(uint256 _maxToIncrease) {
|
||||
uint256 newMp = _increasedMultiplier + _currentMp;
|
||||
return newMp > MAX_MP ? MAX_MP - newMp : _increasedMultiplier;
|
||||
}
|
||||
|
||||
function processEpoch() private {
|
||||
if(block.timestamp >= epochEnd()){
|
||||
//finalize current epoch
|
||||
epochs[currentEpoch].epochReward = epochReward();
|
||||
epochs[currentEpoch].totalSupply = totalSupply();
|
||||
pendingReward += epochs[currentEpoch].epochReward;
|
||||
//create new epoch
|
||||
currentEpoch++;
|
||||
epochs[currentEpoch].startTime = block.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
function processAccount(Account storage account, uint256 _limitEpoch) private {
|
||||
processEpoch();
|
||||
require(address(migration) == address(0), "Contract ended, please migrate");
|
||||
require(_limitEpoch <= currentEpoch, "Non-sense call");
|
||||
uint256 userReward;
|
||||
uint256 userEpoch = account.epoch;
|
||||
for (Epoch memory iEpoch = epochs[userEpoch]; userEpoch < _limitEpoch; userEpoch++) {
|
||||
//mint multipliers to that epoch
|
||||
mintMultiplier(account, iEpoch.startTime + EPOCH_SIZE);
|
||||
uint256 userSupply = account.balance + account.multiplier;
|
||||
uint256 userShare = userSupply / iEpoch.totalSupply; //TODO: might lose precision, multiply by 100 and divide back later?
|
||||
userReward += userShare * iEpoch.epochReward;
|
||||
}
|
||||
account.epoch = userEpoch;
|
||||
if(userReward > 0){
|
||||
pendingReward -= userReward;
|
||||
stakedToken.transfer(account.rewardAddress, userReward);
|
||||
}
|
||||
mintMultiplier(account, block.timestamp);
|
||||
}
|
||||
|
||||
|
||||
function mintMultiplier(Account storage account, uint256 processTime) private {
|
||||
uint256 deltaTime = processTime - account.lastMint;
|
||||
account.lastMint = processTime;
|
||||
uint256 increasedMultiplier = calcMaxMultiplierIncrease(
|
||||
account.balance * (MP_APY * deltaTime),
|
||||
account.multiplier);
|
||||
account.multiplier += increasedMultiplier;
|
||||
multiplierSupply += increasedMultiplier;
|
||||
}
|
||||
|
||||
function mintIntialMultiplier(Account storage account, uint256 lockTime) private {
|
||||
//if balance still locked, multipliers must be minted from difference of time.
|
||||
uint256 dT = account.lockUntil > block.timestamp ? block.timestamp + lockTime - account.lockUntil : lockTime;
|
||||
account.lockUntil = block.timestamp + lockTime;
|
||||
uint256 increasedMultiplier = account.balance * (dT+1); //dT+1 could be replaced by requiring that dT is > 0, as it wouldnt matter 1 second locked.
|
||||
account.lastMint = block.timestamp;
|
||||
account.multiplier += increasedMultiplier;
|
||||
multiplierSupply += increasedMultiplier;
|
||||
}
|
||||
|
||||
function totalSupply() public view returns (uint256) {
|
||||
return multiplierSupply + stakeSupply;
|
||||
}
|
||||
|
||||
function epochReward() public view returns (uint256) {
|
||||
return stakedToken.balanceOf(address(this)) - pendingReward;
|
||||
}
|
||||
|
||||
function epochEnd() public view returns (uint256) {
|
||||
return epochs[currentEpoch].startTime + EPOCH_SIZE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
import "./StakeManager.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
/**
|
||||
* @title StakeVault
|
||||
* @author Ricardo Guilherme Schmidt <ricardo3@status.im>
|
||||
* @notice Secures user stake
|
||||
*/
|
||||
contract StakeVault is Ownable {
|
||||
StakeManager stakeManager;
|
||||
ERC20 stakedToken;
|
||||
|
||||
constructor(address _owner) {
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
|
||||
function stake(uint256 _amount, uint256 _time) external onlyOwner {
|
||||
stakedToken.transferFrom(msg.sender, address(this), _amount);
|
||||
stakeManager.stake(_amount, _time);
|
||||
}
|
||||
|
||||
function lock(uint256 _time) external onlyOwner {
|
||||
stakeManager.lock(_time);
|
||||
}
|
||||
|
||||
function unstake(uint256 _amount) external onlyOwner {
|
||||
stakeManager.unstake(_amount);
|
||||
stakedToken.transferFrom(address(this), msg.sender, _amount);
|
||||
}
|
||||
|
||||
function leave() external onlyOwner {
|
||||
stakeManager.leave();
|
||||
stakedToken.transferFrom(address(this), msg.sender, stakedToken.balanceOf(address(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Opt-in migration to a new StakeManager contract.
|
||||
*/
|
||||
function updateManager() external onlyOwner {
|
||||
StakeManager migrated = stakeManager.migrate();
|
||||
require(address(migrated) != address(0), "Migration not available.");
|
||||
stakeManager = migrated;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract Token is Ownable, ERC20 {
|
||||
|
||||
constructor() ERC20("Status", "SNT") {
|
||||
|
||||
}
|
||||
|
||||
function mint(address _destination, uint256 _amount) external onlyOwner {
|
||||
_mint(_destination, _amount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
/// @dev `Owned` is a base level contract that assigns an `owner` that can be
|
||||
/// later changed
|
||||
contract Owned {
|
||||
|
||||
/// @dev `owner` is the only address that can call a function with this
|
||||
/// modifier
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner);
|
||||
_;
|
||||
}
|
||||
|
||||
address public owner;
|
||||
|
||||
/// @notice The Constructor assigns the message sender to be `owner`
|
||||
constructor() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
address public newOwner;
|
||||
|
||||
/// @notice `owner` can step down and assign some other address to this role
|
||||
/// @param _newOwner The address of the new owner. 0x0 can be used to create
|
||||
/// an unowned neutral vault, however that cannot be undone
|
||||
function changeOwner(address _newOwner) external onlyOwner {
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
|
||||
function acceptOwnership() external {
|
||||
if (msg.sender == newOwner) {
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
import "./SNTPlaceHolder.sol";
|
||||
|
||||
contract SNTFaucet is SNTPlaceHolder {
|
||||
|
||||
bool public open = true;
|
||||
|
||||
constructor(address _owner, address payable _snt) SNTPlaceHolder(_owner, _snt) {
|
||||
|
||||
}
|
||||
|
||||
fallback() external {
|
||||
generateTokens(msg.sender, 1000* (10 ** uint(snt.decimals())));
|
||||
}
|
||||
|
||||
function mint(uint256 _amount) external {
|
||||
require(open);
|
||||
generateTokens(msg.sender, _amount);
|
||||
}
|
||||
|
||||
function setOpen(bool _open) external onlyOwner {
|
||||
open = _open;
|
||||
}
|
||||
|
||||
function destroyTokens(address _who, uint _amount) onlyOwner public {
|
||||
snt.destroyTokens(_who, _amount);
|
||||
}
|
||||
|
||||
function generateTokens(address _who, uint _amount) public {
|
||||
require(msg.sender == owner || open);
|
||||
snt.generateTokens(_who, _amount);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
import "./token/TokenController.sol";
|
||||
import "./token/MiniMeToken.sol";
|
||||
import "./SafeMath.sol";
|
||||
import "./Owned.sol";
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// @title SNTPlaceholder Contract
|
||||
/// @author Jordi Baylina
|
||||
/// @dev The SNTPlaceholder contract will take control over the SNT after the contribution
|
||||
/// is finalized and before the Status Network is deployed.
|
||||
/// The contract allows for SNT transfers and transferFrom and implements the
|
||||
/// logic for transferring control of the token to the network when the offering
|
||||
/// asks it to do so.
|
||||
contract SNTPlaceHolder is TokenController, Owned {
|
||||
using SafeMath for uint256;
|
||||
MiniMeToken public snt;
|
||||
|
||||
constructor(address _owner, address payable _snt) {
|
||||
owner = _owner;
|
||||
snt = MiniMeToken(_snt);
|
||||
}
|
||||
|
||||
/// @notice The owner of this contract can change the controller of the SNT token
|
||||
/// Please, be sure that the owner is a trusted agent or 0x0 address.
|
||||
/// @param _newController The address of the new controller
|
||||
function changeController(address _newController) public onlyOwner {
|
||||
snt.changeController(_newController);
|
||||
emit ControllerChanged(_newController);
|
||||
}
|
||||
|
||||
//////////
|
||||
// MiniMe Controller Interface functions
|
||||
//////////
|
||||
|
||||
// In between the offering and the network. Default settings for allowing token transfers.
|
||||
function proxyPayment(address) override public payable returns (bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function onTransfer(address, address, uint256) override public pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function onApprove(address, address, uint256) override public pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
||||
event ControllerChanged(address indexed _newController);
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
/**
|
||||
* Math operations with safety checks
|
||||
*/
|
||||
library SafeMath {
|
||||
function mul(uint a, uint b) internal pure returns (uint) {
|
||||
uint c = a * b;
|
||||
assert(a == 0 || c / a == b);
|
||||
return c;
|
||||
}
|
||||
|
||||
function div(uint a, uint b) internal pure returns (uint) {
|
||||
// assert(b > 0); // Solidity automatically throws when dividing by 0
|
||||
uint c = a / b;
|
||||
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
||||
return c;
|
||||
}
|
||||
|
||||
function sub(uint a, uint b) internal pure returns (uint) {
|
||||
assert(b <= a);
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function add(uint a, uint b) internal pure returns (uint) {
|
||||
uint c = a + b;
|
||||
assert(c >= a);
|
||||
return c;
|
||||
}
|
||||
|
||||
function max64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function max256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
||||
|
||||
/**
|
||||
* @title IOptimismMintableERC20
|
||||
* @notice This interface is available on the OptimismMintableERC20 contract. We declare it as a
|
||||
* separate interface so that it can be used in custom implementations of
|
||||
* OptimismMintableERC20.
|
||||
*/
|
||||
interface IOptimismMintableERC20 is IERC165 {
|
||||
function remoteToken() external view returns (address);
|
||||
|
||||
function bridge() external returns (address);
|
||||
|
||||
function mint(address _to, uint256 _amount) external;
|
||||
|
||||
function burn(address _from, uint256 _amount) external;
|
||||
}
|
||||
|
||||
/**
|
||||
* @custom:legacy
|
||||
* @title ILegacyMintableERC20
|
||||
* @notice This interface was available on the legacy L2StandardERC20 contract. It remains available
|
||||
* on the OptimismMintableERC20 contract for backwards compatibility.
|
||||
*/
|
||||
interface ILegacyMintableERC20 is IERC165 {
|
||||
function l1Token() external view returns (address);
|
||||
|
||||
function mint(address _to, uint256 _amount) external;
|
||||
|
||||
function burn(address _from, uint256 _amount) external;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
import { MiniMeToken } from "../token/MiniMeToken.sol";
|
||||
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
||||
import { ILegacyMintableERC20, IOptimismMintableERC20 } from "./IOptimismMintableERC20.sol";
|
||||
import { Semver } from "./Semver.sol";
|
||||
|
||||
/// @title OptimismMintableMiniMeToken
|
||||
/// @notice OptimismMintableMiniMeToken is a standard extension of the base MiniMeToken token contract designed
|
||||
/// to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to
|
||||
/// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa.
|
||||
/// Designed to be backwards compatible with the older StandardL2ERC20 token which was only
|
||||
/// meant for use on L2.
|
||||
contract OptimismMintableMiniMeToken is IOptimismMintableERC20, ILegacyMintableERC20, MiniMeToken, Semver {
|
||||
/// @notice Address of the corresponding version of this token on the remote chain.
|
||||
address public immutable REMOTE_TOKEN;
|
||||
|
||||
/// @notice Address of the StandardBridge on this network.
|
||||
address public immutable BRIDGE;
|
||||
|
||||
/// @notice Emitted whenever tokens are minted for an account.
|
||||
/// @param account Address of the account tokens are being minted for.
|
||||
/// @param amount Amount of tokens minted.
|
||||
event Mint(address indexed account, uint256 amount);
|
||||
|
||||
/// @notice Emitted whenever tokens are burned from an account.
|
||||
/// @param account Address of the account tokens are being burned from.
|
||||
/// @param amount Amount of tokens burned.
|
||||
event Burn(address indexed account, uint256 amount);
|
||||
|
||||
/// @notice A modifier that only allows the bridge to call
|
||||
modifier onlyBridge() {
|
||||
require(msg.sender == BRIDGE, "OptimismMintableMiniMeToken: only bridge can mint and burn");
|
||||
_;
|
||||
}
|
||||
|
||||
/// @custom:semver 1.0.1
|
||||
/// @param _bridge Address of the L2 standard bridge.
|
||||
/// @param _remoteToken Address of the corresponding L1 token.
|
||||
/// @param _tokenName ERC20 name.
|
||||
/// @param _tokenSymbol ERC20 symbol.
|
||||
constructor(
|
||||
address _bridge,
|
||||
address _remoteToken,
|
||||
address _tokenFactory,
|
||||
address payable _parentToken,
|
||||
uint _parentSnapShotBlock,
|
||||
string memory _tokenName,
|
||||
uint8 _decimalUnits,
|
||||
string memory _tokenSymbol,
|
||||
bool _transfersEnabled
|
||||
) MiniMeToken(_tokenFactory, _parentToken, _parentSnapShotBlock, _tokenName, _decimalUnits, _tokenSymbol, _transfersEnabled) Semver(1, 0, 1) {
|
||||
REMOTE_TOKEN = _remoteToken;
|
||||
BRIDGE = _bridge;
|
||||
}
|
||||
|
||||
/// @notice Allows the StandardBridge on this network to mint tokens.
|
||||
/// @param _to Address to mint tokens to.
|
||||
/// @param _amount Amount of tokens to mint.
|
||||
function mint(address _to, uint256 _amount)
|
||||
external
|
||||
override(IOptimismMintableERC20, ILegacyMintableERC20)
|
||||
onlyBridge
|
||||
{
|
||||
_mint(_to, _amount);
|
||||
emit Mint(_to, _amount);
|
||||
}
|
||||
|
||||
/// @notice Allows the StandardBridge on this network to burn tokens.
|
||||
/// @param _from Address to burn tokens from.
|
||||
/// @param _amount Amount of tokens to burn.
|
||||
function burn(address _from, uint256 _amount)
|
||||
external
|
||||
override(IOptimismMintableERC20, ILegacyMintableERC20)
|
||||
onlyBridge
|
||||
{
|
||||
_burn(_from, _amount);
|
||||
emit Burn(_from, _amount);
|
||||
}
|
||||
|
||||
/// @notice ERC165 interface check function.
|
||||
/// @param _interfaceId Interface ID to check.
|
||||
/// @return Whether or not the interface is supported by this contract.
|
||||
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
|
||||
bytes4 iface1 = type(IERC165).interfaceId;
|
||||
// Interface corresponding to the legacy L2StandardERC20.
|
||||
bytes4 iface2 = type(ILegacyMintableERC20).interfaceId;
|
||||
// Interface corresponding to the updated OptimismMintableMiniMeToken (this contract).
|
||||
bytes4 iface3 = type(IOptimismMintableERC20).interfaceId;
|
||||
return _interfaceId == iface1 || _interfaceId == iface2 || _interfaceId == iface3;
|
||||
}
|
||||
|
||||
/// @custom:legacy
|
||||
/// @notice Legacy getter for the remote token. Use REMOTE_TOKEN going forward.
|
||||
function l1Token() public view returns (address) {
|
||||
return REMOTE_TOKEN;
|
||||
}
|
||||
|
||||
/// @custom:legacy
|
||||
/// @notice Legacy getter for the bridge. Use BRIDGE going forward.
|
||||
function l2Bridge() public view returns (address) {
|
||||
return BRIDGE;
|
||||
}
|
||||
|
||||
/// @custom:legacy
|
||||
/// @notice Legacy getter for REMOTE_TOKEN.
|
||||
function remoteToken() public view returns (address) {
|
||||
return REMOTE_TOKEN;
|
||||
}
|
||||
|
||||
/// @custom:legacy
|
||||
/// @notice Legacy getter for BRIDGE.
|
||||
function bridge() public view returns (address) {
|
||||
return BRIDGE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
|
||||
|
||||
/// @title Semver
|
||||
/// @notice Semver is a simple contract for managing contract versions.
|
||||
contract Semver {
|
||||
/// @notice Contract version number (major).
|
||||
uint256 private immutable MAJOR_VERSION;
|
||||
|
||||
/// @notice Contract version number (minor).
|
||||
uint256 private immutable MINOR_VERSION;
|
||||
|
||||
/// @notice Contract version number (patch).
|
||||
uint256 private immutable PATCH_VERSION;
|
||||
|
||||
/// @param _major Version number (major).
|
||||
/// @param _minor Version number (minor).
|
||||
/// @param _patch Version number (patch).
|
||||
constructor(
|
||||
uint256 _major,
|
||||
uint256 _minor,
|
||||
uint256 _patch
|
||||
) {
|
||||
MAJOR_VERSION = _major;
|
||||
MINOR_VERSION = _minor;
|
||||
PATCH_VERSION = _patch;
|
||||
}
|
||||
|
||||
/// @notice Returns the full semver contract version.
|
||||
/// @return Semver contract version as a string.
|
||||
function version() public view returns (string memory) {
|
||||
return
|
||||
string(
|
||||
abi.encodePacked(
|
||||
Strings.toString(MAJOR_VERSION),
|
||||
".",
|
||||
Strings.toString(MINOR_VERSION),
|
||||
".",
|
||||
Strings.toString(PATCH_VERSION)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
abstract contract ApproveAndCallFallBack {
|
||||
function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) virtual public;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
|
||||
contract Controlled {
|
||||
string internal constant ERR_BAD_PARAMETER = "Bad parameter";
|
||||
string internal constant ERR_UNAUTHORIZED = "Unauthorized";
|
||||
event NewController(address controller);
|
||||
/// @notice The address of the controller is the only address that can call
|
||||
/// a function with this modifier
|
||||
modifier onlyController {
|
||||
require(msg.sender == controller, "Unauthorized");
|
||||
_;
|
||||
}
|
||||
|
||||
address public controller;
|
||||
|
||||
constructor(address _initController) {
|
||||
require(_initController != address(0), ERR_BAD_PARAMETER);
|
||||
controller = _initController;
|
||||
}
|
||||
|
||||
/// @notice Changes the controller of the contract
|
||||
/// @param _newController The new controller of the contract
|
||||
function changeController(address _newController) public onlyController {
|
||||
controller = _newController;
|
||||
emit NewController(_newController);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
/*
|
||||
Copyright 2016, Jordi Baylina
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @title MiniMeToken Contract
|
||||
* @author Jordi Baylina
|
||||
* @dev This token contract's goal is to make it easy for anyone to clone this
|
||||
* token using the token distribution at a given block, this will allow DAO's
|
||||
* and DApps to upgrade their features in a decentralized manner without
|
||||
* affecting the original token
|
||||
* @dev It is ERC20 compliant, but still needs to under go further testing.
|
||||
*/
|
||||
|
||||
import "./Controlled.sol";
|
||||
import "./TokenController.sol";
|
||||
import "./ApproveAndCallFallBack.sol";
|
||||
import "./MiniMeTokenFactory.sol";
|
||||
|
||||
/**
|
||||
* @dev The actual token contract, the default controller is the msg.sender
|
||||
* that deploys the contract, so usually this token will be deployed by a
|
||||
* token controller contract, which Giveth will call a "Campaign"
|
||||
*/
|
||||
contract MiniMeToken is Controlled {
|
||||
|
||||
string public name; //The Token's name: e.g. DigixDAO Tokens
|
||||
uint8 public decimals; //Number of decimals of the smallest unit
|
||||
string public symbol; //An identifier: e.g. REP
|
||||
string public token_version = "MMT_0.1"; //An arbitrary versioning scheme
|
||||
|
||||
/**
|
||||
* @dev `Checkpoint` is the structure that attaches a block number to a
|
||||
* given value, the block number attached is the one that last changed the
|
||||
* value
|
||||
*/
|
||||
struct Checkpoint {
|
||||
|
||||
// `fromBlock` is the block number that the value was generated from
|
||||
uint128 fromBlock;
|
||||
|
||||
// `value` is the amount of tokens at a specific block number
|
||||
uint128 value;
|
||||
}
|
||||
|
||||
// `parentToken` is the Token address that was cloned to produce this token;
|
||||
// it will be 0x0 for a token that was not cloned
|
||||
MiniMeToken public parentToken;
|
||||
|
||||
// `parentSnapShotBlock` is the block number from the Parent Token that was
|
||||
// used to determine the initial distribution of the Clone Token
|
||||
uint public parentSnapShotBlock;
|
||||
|
||||
// `creationBlock` is the block number that the Clone Token was created
|
||||
uint public creationBlock;
|
||||
|
||||
// `balances` is the map that tracks the balance of each address, in this
|
||||
// contract when the balance changes the block number that the change
|
||||
// occurred is also included in the map
|
||||
mapping (address => Checkpoint[]) balances;
|
||||
|
||||
// `allowed` tracks any extra transfer rights as in all ERC20 tokens
|
||||
mapping (address => mapping (address => uint256)) allowed;
|
||||
|
||||
// Tracks the history of the `totalSupply` of the token
|
||||
Checkpoint[] totalSupplyHistory;
|
||||
|
||||
// Flag that determines if the token is transferable or not.
|
||||
bool public transfersEnabled;
|
||||
|
||||
// The factory used to create new clone tokens
|
||||
MiniMeTokenFactory public tokenFactory;
|
||||
|
||||
////////////////
|
||||
// Constructor
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @notice Constructor to create a MiniMeToken
|
||||
* @param _tokenFactory The address of the MiniMeTokenFactory contract that
|
||||
* will create the Clone token contracts, the token factory needs to be
|
||||
* deployed first
|
||||
* @param _parentToken Address of the parent token, set to 0x0 if it is a
|
||||
* new token
|
||||
* @param _parentSnapShotBlock Block of the parent token that will
|
||||
* determine the initial distribution of the clone token, set to 0 if it
|
||||
* is a new token
|
||||
* @param _tokenName Name of the new token
|
||||
* @param _decimalUnits Number of decimals of the new token
|
||||
* @param _tokenSymbol Token Symbol for the new token
|
||||
* @param _transfersEnabled If true, tokens will be able to be transferred
|
||||
*/
|
||||
constructor(
|
||||
address _tokenFactory,
|
||||
address payable _parentToken,
|
||||
uint _parentSnapShotBlock,
|
||||
string memory _tokenName,
|
||||
uint8 _decimalUnits,
|
||||
string memory _tokenSymbol,
|
||||
bool _transfersEnabled
|
||||
)
|
||||
Controlled(msg.sender)
|
||||
{
|
||||
tokenFactory = MiniMeTokenFactory(_tokenFactory);
|
||||
name = _tokenName; // Set the name
|
||||
decimals = _decimalUnits; // Set the decimals
|
||||
symbol = _tokenSymbol; // Set the symbol
|
||||
parentToken = MiniMeToken(_parentToken);
|
||||
parentSnapShotBlock = _parentSnapShotBlock;
|
||||
transfersEnabled = _transfersEnabled;
|
||||
creationBlock = block.number;
|
||||
}
|
||||
|
||||
|
||||
///////////////////
|
||||
// ERC20 Methods
|
||||
///////////////////
|
||||
|
||||
/**
|
||||
* @notice Send `_amount` tokens to `_to` from `msg.sender`
|
||||
* @param _to The address of the recipient
|
||||
* @param _amount The amount of tokens to be transferred
|
||||
* @return success Whether the transfer was successful or not
|
||||
*/
|
||||
function transfer(address _to, uint256 _amount) public returns (bool success) {
|
||||
require(transfersEnabled, "Transfers disabled");
|
||||
return doTransfer(msg.sender, _to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Send `_amount` tokens to `_to` from `_from` on the condition it
|
||||
* is approved by `_from`
|
||||
* @param _from The address holding the tokens being transferred
|
||||
* @param _to The address of the recipient
|
||||
* @param _amount The amount of tokens to be transferred
|
||||
* @return success True if the transfer was successful
|
||||
*/
|
||||
function transferFrom(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount
|
||||
)
|
||||
public
|
||||
returns (bool success)
|
||||
{
|
||||
|
||||
// The controller of this contract can move tokens around at will,
|
||||
// this is important to recognize! Confirm that you trust the
|
||||
// controller of this contract, which in most situations should be
|
||||
// another open source smart contract or 0x0
|
||||
if (msg.sender != controller) {
|
||||
require(transfersEnabled, "Transfers disabled");
|
||||
|
||||
// The standard ERC 20 transferFrom functionality
|
||||
if (allowed[_from][msg.sender] < _amount) {
|
||||
return false;
|
||||
}
|
||||
allowed[_from][msg.sender] -= _amount;
|
||||
}
|
||||
return doTransfer(_from, _to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This is the actual transfer function in the token contract, it can
|
||||
* only be called by other functions in this contract.
|
||||
* @param _from The address holding the tokens being transferred
|
||||
* @param _to The address of the recipient
|
||||
* @param _amount The amount of tokens to be transferred
|
||||
* @return True if the transfer was successful
|
||||
*/
|
||||
function doTransfer(
|
||||
address _from,
|
||||
address _to,
|
||||
uint _amount
|
||||
)
|
||||
internal
|
||||
returns(bool)
|
||||
{
|
||||
|
||||
if (_amount == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
require(parentSnapShotBlock < block.number, "Invalid block.number");
|
||||
|
||||
// Do not allow transfer to 0x0 or the token contract itself
|
||||
require((_to != address(0)) && (_to != address(this)), "Invalid _to");
|
||||
|
||||
// If the amount being transfered is more than the balance of the
|
||||
// account the transfer returns false
|
||||
uint256 previousBalanceFrom = balanceOfAt(_from, block.number);
|
||||
if (previousBalanceFrom < _amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Alerts the token controller of the transfer
|
||||
if (isContract(controller)) {
|
||||
require(TokenController(controller).onTransfer(_from, _to, _amount), "Unauthorized transfer");
|
||||
}
|
||||
|
||||
// First update the balance array with the new value for the address
|
||||
// sending the tokens
|
||||
updateValueAtNow(balances[_from], previousBalanceFrom - _amount);
|
||||
|
||||
// Then update the balance array with the new value for the address
|
||||
// receiving the tokens
|
||||
uint256 previousBalanceTo = balanceOfAt(_to, block.number);
|
||||
require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow
|
||||
updateValueAtNow(balances[_to], previousBalanceTo + _amount);
|
||||
|
||||
// An event to make the transfer easy to find on the blockchain
|
||||
emit Transfer(_from, _to, _amount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function doApprove(
|
||||
address _from,
|
||||
address _spender,
|
||||
uint256 _amount
|
||||
)
|
||||
internal
|
||||
returns (bool)
|
||||
{
|
||||
require(transfersEnabled, "Transfers disabled");
|
||||
|
||||
// To change the approve amount you first have to reduce the addresses`
|
||||
// allowance to zero by calling `approve(_spender,0)` if it is not
|
||||
// already 0 to mitigate the race condition described here:
|
||||
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
|
||||
require((_amount == 0) || (allowed[_from][_spender] == 0), "Reset allowance first");
|
||||
|
||||
// Alerts the token controller of the approve function call
|
||||
if (isContract(controller)) {
|
||||
require(TokenController(controller).onApprove(_from, _spender, _amount), "Unauthorized approve");
|
||||
}
|
||||
|
||||
allowed[_from][_spender] = _amount;
|
||||
emit Approval(_from, _spender, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function _mint(
|
||||
address _owner,
|
||||
uint _amount
|
||||
)
|
||||
internal
|
||||
{
|
||||
uint curTotalSupply = totalSupplyAt(block.number);
|
||||
require(curTotalSupply + _amount >= curTotalSupply, "Total overflow"); // Check for overflow
|
||||
uint previousBalanceTo = balanceOfAt(_owner, block.number);
|
||||
require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow
|
||||
updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
|
||||
updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
|
||||
emit Transfer(address(0), _owner, _amount);
|
||||
}
|
||||
|
||||
function _burn(
|
||||
address _owner,
|
||||
uint _amount
|
||||
)
|
||||
internal
|
||||
{
|
||||
uint curTotalSupply = totalSupplyAt(block.number);
|
||||
require(curTotalSupply >= _amount, "No enough supply");
|
||||
uint previousBalanceFrom = balanceOfAt(_owner, block.number);
|
||||
require(previousBalanceFrom >= _amount, "No enough balance");
|
||||
updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
|
||||
updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
|
||||
emit Transfer(_owner, address(0), _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _owner The address that's balance is being requested
|
||||
* @return balance The balance of `_owner` at the current block
|
||||
*/
|
||||
function balanceOf(address _owner) external view returns (uint256 balance) {
|
||||
return balanceOfAt(_owner, block.number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
|
||||
* its behalf. This is a modified version of the ERC20 approve function
|
||||
* to be a little bit safer
|
||||
* @param _spender The address of the account able to transfer the tokens
|
||||
* @param _amount The amount of tokens to be approved for transfer
|
||||
* @return success True if the approval was successful
|
||||
*/
|
||||
function approve(address _spender, uint256 _amount) external returns (bool success) {
|
||||
return doApprove(msg.sender, _spender, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This function makes it easy to read the `allowed[]` map
|
||||
* @param _owner The address of the account that owns the token
|
||||
* @param _spender The address of the account able to transfer the tokens
|
||||
* @return remaining Amount of remaining tokens of _owner that _spender is allowed
|
||||
* to spend
|
||||
*/
|
||||
function allowance(
|
||||
address _owner,
|
||||
address _spender
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 remaining)
|
||||
{
|
||||
return allowed[_owner][_spender];
|
||||
}
|
||||
/**
|
||||
* @notice `msg.sender` approves `_spender` to send `_amount` tokens on
|
||||
* its behalf, and then a function is triggered in the contract that is
|
||||
* being approved, `_spender`. This allows users to use their tokens to
|
||||
* interact with contracts in one function call instead of two
|
||||
* @param _spender The address of the contract able to transfer the tokens
|
||||
* @param _amount The amount of tokens to be approved for transfer
|
||||
* @return success True if the function call was successful
|
||||
*/
|
||||
function approveAndCall(
|
||||
address _spender,
|
||||
uint256 _amount,
|
||||
bytes memory _extraData
|
||||
)
|
||||
public
|
||||
returns (bool success)
|
||||
{
|
||||
require(doApprove(msg.sender, _spender, _amount), "Approve failed");
|
||||
|
||||
ApproveAndCallFallBack(_spender).receiveApproval(
|
||||
msg.sender,
|
||||
_amount,
|
||||
address(this),
|
||||
_extraData
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This function makes it easy to get the total number of tokens
|
||||
* @return The total number of tokens
|
||||
*/
|
||||
function totalSupply() external view returns (uint) {
|
||||
return totalSupplyAt(block.number);
|
||||
}
|
||||
|
||||
|
||||
////////////////
|
||||
// Query balance and totalSupply in History
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @dev Queries the balance of `_owner` at a specific `_blockNumber`
|
||||
* @param _owner The address from which the balance will be retrieved
|
||||
* @param _blockNumber The block number when the balance is queried
|
||||
* @return The balance at `_blockNumber`
|
||||
*/
|
||||
function balanceOfAt(
|
||||
address _owner,
|
||||
uint _blockNumber
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint)
|
||||
{
|
||||
|
||||
// These next few lines are used when the balance of the token is
|
||||
// requested before a check point was ever created for this token, it
|
||||
// requires that the `parentToken.balanceOfAt` be queried at the
|
||||
// genesis block for that token as this contains initial balance of
|
||||
// this token
|
||||
if ((balances[_owner].length == 0) || (balances[_owner][0].fromBlock > _blockNumber)) {
|
||||
if (address(parentToken) != address(0)) {
|
||||
return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
|
||||
} else {
|
||||
// Has no parent
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This will return the expected balance during normal situations
|
||||
} else {
|
||||
return getValueAt(balances[_owner], _blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Total amount of tokens at a specific `_blockNumber`.
|
||||
* @param _blockNumber The block number when the totalSupply is queried
|
||||
* @return The total amount of tokens at `_blockNumber`
|
||||
*/
|
||||
function totalSupplyAt(uint _blockNumber) public view returns(uint) {
|
||||
|
||||
// These next few lines are used when the totalSupply of the token is
|
||||
// requested before a check point was ever created for this token, it
|
||||
// requires that the `parentToken.totalSupplyAt` be queried at the
|
||||
// genesis block for this token as that contains totalSupply of this
|
||||
// token at this block number.
|
||||
if ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) {
|
||||
if (address(parentToken) != address(0)) {
|
||||
return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This will return the expected totalSupply during normal situations
|
||||
} else {
|
||||
return getValueAt(totalSupplyHistory, _blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Clone Token Method
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @notice Creates a new clone token with the initial distribution being
|
||||
* this token at `snapshotBlock`
|
||||
* @param _cloneTokenName Name of the clone token
|
||||
* @param _cloneDecimalUnits Number of decimals of the smallest unit
|
||||
* @param _cloneTokenSymbol Symbol of the clone token
|
||||
* @param _snapshotBlock Block when the distribution of the parent token is
|
||||
* copied to set the initial distribution of the new clone token;
|
||||
* if the block is zero than the actual block, the current block is used
|
||||
* @param _transfersEnabled True if transfers are allowed in the clone
|
||||
* @return The address of the new MiniMeToken Contract
|
||||
*/
|
||||
function createCloneToken(
|
||||
string memory _cloneTokenName,
|
||||
uint8 _cloneDecimalUnits,
|
||||
string memory _cloneTokenSymbol,
|
||||
uint _snapshotBlock,
|
||||
bool _transfersEnabled
|
||||
)
|
||||
public
|
||||
returns(address)
|
||||
{
|
||||
uint snapshotBlock = _snapshotBlock;
|
||||
if (snapshotBlock == 0) {
|
||||
snapshotBlock = block.number;
|
||||
}
|
||||
MiniMeToken cloneToken = tokenFactory.createCloneToken(
|
||||
payable(this),
|
||||
snapshotBlock,
|
||||
_cloneTokenName,
|
||||
_cloneDecimalUnits,
|
||||
_cloneTokenSymbol,
|
||||
_transfersEnabled
|
||||
);
|
||||
|
||||
cloneToken.changeController(msg.sender);
|
||||
|
||||
// An event to make the token easy to find on the blockchain
|
||||
emit NewCloneToken(address(cloneToken), snapshotBlock);
|
||||
return address(cloneToken);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Generate and destroy tokens
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @notice Generates `_amount` tokens that are assigned to `_owner`
|
||||
* @param _owner The address that will be assigned the new tokens
|
||||
* @param _amount The quantity of tokens generated
|
||||
* @return True if the tokens are generated correctly
|
||||
*/
|
||||
function generateTokens(
|
||||
address _owner,
|
||||
uint _amount
|
||||
)
|
||||
public
|
||||
onlyController
|
||||
returns (bool)
|
||||
{
|
||||
_mint(_owner, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Burns `_amount` tokens from `_owner`
|
||||
* @param _owner The address that will lose the tokens
|
||||
* @param _amount The quantity of tokens to burn
|
||||
* @return True if the tokens are burned correctly
|
||||
*/
|
||||
function destroyTokens(
|
||||
address _owner,
|
||||
uint _amount
|
||||
)
|
||||
public
|
||||
onlyController
|
||||
returns (bool)
|
||||
{
|
||||
_burn(_owner, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Enable tokens transfers
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @notice Enables token holders to transfer their tokens freely if true
|
||||
* @param _transfersEnabled True if transfers are allowed in the clone
|
||||
*/
|
||||
function enableTransfers(bool _transfersEnabled) public onlyController {
|
||||
transfersEnabled = _transfersEnabled;
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Internal helper functions to query and set a value in a snapshot array
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @dev `getValueAt` retrieves the number of tokens at a given block number
|
||||
* @param checkpoints The history of values being queried
|
||||
* @param _block The block number to retrieve the value at
|
||||
* @return The number of tokens being queried
|
||||
*/
|
||||
function getValueAt(
|
||||
Checkpoint[] storage checkpoints,
|
||||
uint _block
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint)
|
||||
{
|
||||
if (checkpoints.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Shortcut for the actual value
|
||||
if (_block >= checkpoints[checkpoints.length-1].fromBlock) {
|
||||
return checkpoints[checkpoints.length-1].value;
|
||||
}
|
||||
if (_block < checkpoints[0].fromBlock) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Binary search of the value in the array
|
||||
uint _min = 0;
|
||||
uint max = checkpoints.length-1;
|
||||
while (max > _min) {
|
||||
uint mid = (max + _min + 1) / 2;
|
||||
if (checkpoints[mid].fromBlock<=_block) {
|
||||
_min = mid;
|
||||
} else {
|
||||
max = mid-1;
|
||||
}
|
||||
}
|
||||
return checkpoints[_min].value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev `updateValueAtNow` used to update the `balances` map and the
|
||||
* `totalSupplyHistory`
|
||||
* @param checkpoints The history of data being updated
|
||||
* @param _value The new number of tokens
|
||||
*/
|
||||
function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal {
|
||||
if ((checkpoints.length == 0) || (checkpoints[checkpoints.length -1].fromBlock < block.number)) {
|
||||
Checkpoint storage newCheckPoint = checkpoints.push();
|
||||
newCheckPoint.fromBlock = uint128(block.number);
|
||||
newCheckPoint.value = uint128(_value);
|
||||
} else {
|
||||
Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
|
||||
oldCheckPoint.value = uint128(_value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to determine if an address is a contract
|
||||
* @param _addr The address being queried
|
||||
* @return True if `_addr` is a contract
|
||||
*/
|
||||
function isContract(address _addr) internal view returns(bool) {
|
||||
uint size;
|
||||
if (_addr == address(0)){
|
||||
return false;
|
||||
}
|
||||
assembly {
|
||||
size := extcodesize(_addr)
|
||||
}
|
||||
return size>0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Helper function to return a min betwen the two uints
|
||||
*/
|
||||
function min(uint a, uint b) internal pure returns (uint) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice The fallback function: If the contract's controller has not been
|
||||
* set to 0, then the `proxyPayment` method is called which relays the
|
||||
* ether and creates tokens as described in the token controller contract
|
||||
*/
|
||||
receive() payable external {
|
||||
require(isContract(controller), "Deposit unallowed");
|
||||
require(TokenController(controller).proxyPayment{value: msg.value}(msg.sender), "Deposit denied");
|
||||
}
|
||||
|
||||
//////////
|
||||
// Safety Methods
|
||||
//////////
|
||||
|
||||
/**
|
||||
* @notice This method can be used by the controller to extract mistakenly
|
||||
* sent tokens to this contract.
|
||||
* @param _token The address of the token contract that you want to recover
|
||||
* set to 0 in case you want to extract ether.
|
||||
*/
|
||||
function claimTokens(address payable _token) public onlyController {
|
||||
if (_token == address(0)) {
|
||||
payable(controller).transfer(address(this).balance);
|
||||
return;
|
||||
}
|
||||
|
||||
MiniMeToken token = MiniMeToken(_token);
|
||||
uint balance = token.balanceOf(address(this));
|
||||
token.transfer(controller, balance);
|
||||
emit ClaimedTokens(_token, controller, balance);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Events
|
||||
////////////////
|
||||
event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount);
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
|
||||
event NewCloneToken(address indexed _cloneToken, uint snapshotBlock);
|
||||
event Approval(
|
||||
address indexed _owner,
|
||||
address indexed _spender,
|
||||
uint256 _amount
|
||||
);
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
import "./MiniMeToken.sol";
|
||||
|
||||
////////////////
|
||||
// MiniMeTokenFactory
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* @dev This contract is used to generate clone contracts from a contract.
|
||||
* In solidity this is the way to create a contract from a contract of the
|
||||
* same class
|
||||
*/
|
||||
contract MiniMeTokenFactory {
|
||||
|
||||
/**
|
||||
* @notice Update the DApp by creating a new token with new functionalities
|
||||
* the msg.sender becomes the controller of this clone token
|
||||
* @param _parentToken Address of the token being cloned
|
||||
* @param _snapshotBlock Block of the parent token that will
|
||||
* determine the initial distribution of the clone token
|
||||
* @param _tokenName Name of the new token
|
||||
* @param _decimalUnits Number of decimals of the new token
|
||||
* @param _tokenSymbol Token Symbol for the new token
|
||||
* @param _transfersEnabled If true, tokens will be able to be transferred
|
||||
* @return The address of the new token contract
|
||||
*/
|
||||
function createCloneToken(
|
||||
address payable _parentToken,
|
||||
uint _snapshotBlock,
|
||||
string memory _tokenName,
|
||||
uint8 _decimalUnits,
|
||||
string memory _tokenSymbol,
|
||||
bool _transfersEnabled
|
||||
) public returns (MiniMeToken) {
|
||||
MiniMeToken newToken = new MiniMeToken(
|
||||
address(this),
|
||||
_parentToken,
|
||||
_snapshotBlock,
|
||||
_tokenName,
|
||||
_decimalUnits,
|
||||
_tokenSymbol,
|
||||
_transfersEnabled
|
||||
);
|
||||
|
||||
newToken.changeController(msg.sender);
|
||||
return newToken;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.18;
|
||||
/**
|
||||
* @dev The token controller contract must implement these functions
|
||||
*/
|
||||
interface TokenController {
|
||||
/**
|
||||
* @notice Called when `_owner` sends ether to the MiniMe Token contract
|
||||
* @param _owner The address that sent the ether to create tokens
|
||||
* @return True if the ether is accepted, false if it throws
|
||||
*/
|
||||
function proxyPayment(address _owner) external payable returns(bool);
|
||||
|
||||
/**
|
||||
* @notice Notifies the controller about a token transfer allowing the
|
||||
* controller to react if desired
|
||||
* @param _from The origin of the transfer
|
||||
* @param _to The destination of the transfer
|
||||
* @param _amount The amount of the transfer
|
||||
* @return False if the controller does not authorize the transfer
|
||||
*/
|
||||
function onTransfer(address _from, address _to, uint _amount) external returns(bool);
|
||||
|
||||
/**
|
||||
* @notice Notifies the controller about an approval allowing the
|
||||
* controller to react if desired
|
||||
* @param _owner The address that calls `approve()`
|
||||
* @param _spender The spender in the `approve()` call
|
||||
* @param _amount The amount in the `approve()` call
|
||||
* @return False if the controller does not authorize the approval
|
||||
*/
|
||||
function onApprove(address _owner, address _spender, uint _amount) external
|
||||
returns(bool);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
require("@nomicfoundation/hardhat-toolbox");
|
||||
require("dotenv").config();
|
||||
|
||||
const INFURA_API_KEY = process.env.INFURA_API_KEY;
|
||||
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;
|
||||
const GOERLI_PRIVATE_KEY = process.env.GOERLI_PRIVATE_KEY;
|
||||
|
||||
/** @type import('hardhat/config').HardhatUserConfig */
|
||||
module.exports = {
|
||||
solidity: "0.8.18",
|
||||
networks: {
|
||||
'optimism-goerli': {
|
||||
chainId: 420,
|
||||
url: `https://opt-goerli.g.alchemy.com/v2/${process.env.L2_ALCHEMY_KEY}`,
|
||||
accounts: { mnemonic: process.env.MNEMONIC }
|
||||
},
|
||||
'optimism-mainnet': {
|
||||
chainId: 10,
|
||||
url: `https://opt-mainnet.g.alchemy.com/v2/${process.env.L2_ALCHEMY_KEY}`,
|
||||
accounts: { mnemonic: process.env.MNEMONIC }
|
||||
},
|
||||
'goerli': {
|
||||
url: `https://goerli.infura.io/v3/${INFURA_API_KEY}`,
|
||||
accounts: [GOERLI_PRIVATE_KEY]
|
||||
}
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: ETHERSCAN_API_KEY,
|
||||
},
|
||||
mocha: {
|
||||
timeout: 100000000,
|
||||
}
|
||||
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "staking",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "hardhat.config.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-toolbox": "^3.0.0",
|
||||
"@openzeppelin/contracts": "^4.9.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"hardhat": "^2.16.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// We require the Hardhat Runtime Environment explicitly here. This is optional
|
||||
// but useful for running the script in a standalone fashion through `node <script>`.
|
||||
//
|
||||
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
|
||||
// will compile your contracts, add the Hardhat Runtime Environment's members to the
|
||||
// global scope, and execute the script.
|
||||
const hre = require("hardhat");
|
||||
|
||||
async function main() {
|
||||
const [deployer] = await ethers.getSigners();
|
||||
console.log(`Deploying contracts to ${network.name} (${network.config.chainId}) with the account: ${deployer.address}`);
|
||||
const miniMeTokenFactory = await ethers.deployContract("MiniMeTokenFactory");
|
||||
const miniMeToken = await ethers.deployContract(
|
||||
"MiniMeToken", [
|
||||
miniMeTokenFactory.target,
|
||||
ethers.ZeroAddress,
|
||||
0,
|
||||
network.config.chainId == 1 ? "Status Network Token" : "Status Test Token",
|
||||
18,
|
||||
network.config.chainId == 1 ? "SNT" : "STT",
|
||||
true
|
||||
]);
|
||||
|
||||
const tokenController = await ethers.deployContract(
|
||||
network.config.chainId == 1 ? "SNTPlaceHolder" : "SNTFaucet",
|
||||
[
|
||||
deployer.address,
|
||||
miniMeToken.target
|
||||
]
|
||||
);
|
||||
await miniMeToken.changeController(tokenController.target);
|
||||
console.log(
|
||||
`${network.config.chainId == 1 ? "SNT" : "STT"} ${miniMeToken.target} controlled by ${await miniMeToken.controller()}`
|
||||
);
|
||||
console.log(
|
||||
`${network.config.chainId == 1 ? "SNTPlaceHolder" : "SNTFaucet"} ${tokenController.target} owned by ${await tokenController.owner()}`
|
||||
);
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
// and properly handle errors.
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
const {
|
||||
loadFixture
|
||||
} = require("@nomicfoundation/hardhat-network-helpers");
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
describe("StakeManager contract", function () {
|
||||
async function deployTokenFixture() {
|
||||
const [owner, addr1, addr2, addr3] = await ethers.getSigners();
|
||||
const testToken = await ethers.deployContract("Token");
|
||||
await Promise.all([ testToken.mint(addr1.address, 12 * 10^18),
|
||||
testToken.mint(addr2.address, 24 * 10^18),
|
||||
testToken.mint(addr3.address, 36 * 10^18)]);
|
||||
return { testToken, owner, addr1, addr2, addr3 };
|
||||
}
|
||||
|
||||
async function deployStakeManager() {
|
||||
const { testToken, owner } = await loadFixture(deployTokenFixture);
|
||||
console.log(testToken)
|
||||
const stakeManager = await ethers.deployContract("StakeManager", [testToken.target, "0x0000000000000000000000000000000000000000"]);
|
||||
return { stakeManager, testToken, owner };
|
||||
}
|
||||
|
||||
|
||||
it("Deployment should be initialized zero", async function () {
|
||||
const { stakeManager, testToken, owner } = await loadFixture(deployStakeManager);
|
||||
expect(await stakeManager.totalSupply()).to.equal(0);
|
||||
expect(await stakeManager.totalSupply()).to.equal(0);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue