Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./Ownable.sol";
import "./Stakeable.sol";
/**
* @notice AlfaCoin is a development token that we use to learn how to code solidity
* and what BEP-20 interface requires
*/
contract AlfaCoin is Ownable, Stakeable {
/**
* @notice Our Tokens required variables that are needed to operate everything
*/
uint256 private _totalSupply;
uint8 private _decimals;
string private _symbol;
string private _name;
/**
* @notice _balances is a mapping that contains a address as KEY
* and the balance of the address as the value
*/
mapping(address => uint256) private _balances;
/**
* @notice _allowances is used to manage and control allownace
* An allowance is the right to use another accounts balance, or part of it
*/
mapping(address => mapping(address => uint256)) private _allowances;
/**
* @notice Events are created below.
* Transfer event is a event that notify the blockchain that a transfer of assets has taken place
*
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @notice Approval is emitted when a new Spender is approved to spend Tokens on
* the Owners account
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
/**
* @notice constructor will be triggered when we create the Smart contract
* _name = name of the token
* _short_symbol = Short Symbol name for the token
* token_decimals = The decimal precision of the Token, defaults 18
* _totalSupply is how much Tokens there are totally
*/
constructor(
string memory token_name,
string memory short_symbol,
uint8 token_decimals,
uint256 token_totalSupply
) {
_name = token_name;
_symbol = short_symbol;
_decimals = token_decimals;
_totalSupply = token_totalSupply;
// Add all the tokens created to the creator of the token
_balances[msg.sender] = _totalSupply;
// Emit an Transfer event to notify the blockchain that an Transfer has occured
emit Transfer(address(0), msg.sender, _totalSupply);
}
/**
* @notice decimals will return the number of decimal precision the Token is deployed with
*/
function decimals() external view returns (uint8) {
return _decimals;
}
/**
* @notice symbol will return the Token's symbol
*/
function symbol() external view returns (string memory) {
return _symbol;
}
/**
* @notice name will return the Token's symbol
*/
function name() external view returns (string memory) {
return _name;
}
/**
* @notice totalSupply will return the tokens total supply of tokens
*/
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
/**
* @notice balanceOf will return the account balance for the given account
*/
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
/**
* @notice _mint will create tokens on the address inputted and then increase the total supply
*
* It will also emit an Transfer event, with sender set to zero address (adress(0))
*
* Requires that the address that is recieveing the tokens is not zero address
*/
function _mint(address account, uint256 amount) internal {
require(account != address(0), "AlfaCoin: cannot mint to zero address");
// Increase total supply
_totalSupply = _totalSupply + (amount);
// Add amount to the account balance using the balance mapping
_balances[account] = _balances[account] + amount;
// Emit our event to log the action
emit Transfer(address(0), account, amount);
}
/**
* @notice _burn will destroy tokens from an address inputted and then decrease total supply
* An Transfer event will emit with receiever set to zero address
*
* Requires
* - Account cannot be zero
* - Account balance has to be bigger or equal to amount
*/
function _burn(address account, uint256 amount) internal {
require(
account != address(0),
"AlfaCoin: cannot burn from zero address"
);
require(
_balances[account] >= amount,
"AlfaCoin: Cannot burn more than the account owns"
);
// Remove the amount from the account balance
_balances[account] = _balances[account] - amount;
// Decrease totalSupply
_totalSupply = _totalSupply - amount;
// Emit event, use zero address as reciever
emit Transfer(account, address(0), amount);
}
/**
* @notice burn is used to destroy tokens on an address
*
* See {_burn}
* Requires
* - msg.sender must be the token owner
*
*/
function burn(address account, uint256 amount)
public
onlyOwner
returns (bool)
{
_burn(account, amount);
return true;
}
/**
* @notice mint is used to create tokens and assign them to msg.sender
*
* See {_mint}
* Requires
* - msg.sender must be the token owner
*
*/
function mint(address account, uint256 amount)
public
onlyOwner
returns (bool)
{
_mint(account, amount);
return true;
}
/**
* @notice transfer is used to transfer funds from the sender to the recipient
* This function is only callable from outside the contract. For internal usage see
* _transfer
*
* Requires
* - Caller cannot be zero
* - Caller must have a balance = or bigger than amount
*
*/
function transfer(address recipient, uint256 amount)
external
returns (bool)
{
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @notice _transfer is used for internal transfers
*
* Events
* - Transfer
*
* Requires
* - Sender cannot be zero
* - recipient cannot be zero
* - sender balance most be = or bigger than amount
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal {
require(sender != address(0), "AlfaCoin: transfer from zero address");
require(recipient != address(0), "AlfaCoin: transfer to zero address");
require(
_balances[sender] >= amount,
"AlfaCoin: cant transfer more than your account holds"
);
_balances[sender] = _balances[sender] - amount;
_balances[recipient] = _balances[recipient] + amount;
emit Transfer(sender, recipient, amount);
}
/**
* @notice getOwner just calls Ownables owner function.
* returns owner of the token
*
*/
function getOwner() external view returns (address) {
return owner();
}
/**
* @notice allowance is used view how much allowance an spender has
*/
function allowance(address owner, address spender)
external
view
returns (uint256)
{
return _allowances[owner][spender];
}
/**
* @notice approve will use the senders address and allow the spender to use X amount of tokens on his behalf
*/
function approve(address spender, uint256 amount) external returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
/**
* @notice _approve is used to add a new Spender to a Owners account
*
* Events
* - {Approval}
*
* Requires
* - owner and spender cannot be zero address
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal {
require(
owner != address(0),
"AlfaCoin: approve cannot be done from zero address"
);
require(
spender != address(0),
"AlfaCoin: approve cannot be to zero address"
);
// Set the allowance of the spender address at the Owner mapping over accounts to the amount
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @notice transferFrom is uesd to transfer Tokens from a Accounts allowance
* Spender address should be the token holder
*
* Requires
* - The caller must have a allowance = or bigger than the amount spending
*/
function transferFrom(
address spender,
address recipient,
uint256 amount
) external returns (bool) {
// Make sure spender is allowed the amount
require(
_allowances[spender][msg.sender] >= amount,
"AlfaCoin: You cannot spend that much on this account"
);
// Transfer first
_transfer(spender, recipient, amount);
// Reduce current allowance so a user cannot respend
_approve(
spender,
msg.sender,
_allowances[spender][msg.sender] - amount
);
return true;
}
/**
* @notice increaseAllowance
* Adds allowance to a account from the function caller address
*/
function increaseAllowance(address spender, uint256 amount)
public
returns (bool)
{
_approve(
msg.sender,
spender,
_allowances[msg.sender][spender] + amount
);
return true;
}
/**
* @notice decreaseAllowance
* Decrease the allowance on the account inputted from the caller address
*/
function decreaseAllowance(address spender, uint256 amount)
public
returns (bool)
{
_approve(
msg.sender,
spender,
_allowances[msg.sender][spender] - amount
);
return true;
}
/**
* Add functionality like burn to the _stake afunction
*
*/
function stake(uint256 _amount) public {
// Make sure staker actually is good for it
require(_totalSupply < 108000000000000000000000000, "AlfaCoin: Cannot stake more");
require(
_amount <= _balances[msg.sender],
"AlfaCoin: Cannot stake more than you own"
);
_stake(_amount);
// Burn the amount of tokens on the sender
_burn(msg.sender, _amount);
}
/**
* @notice withdrawStake is used to withdraw stakes from the account holder
*/
function withdrawStake(uint256 amount, uint256 stake_index) public {
uint256 amount_to_mint = _withdrawStake(amount, stake_index);
// Return staked tokens to user
_mint(msg.sender, amount_to_mint);
}
function withdrawAll()public {
uint256 amount_to_mint = _withdrawAll();
// Return staked tokens to user
_mint(msg.sender, amount_to_mint);
}
function viewRewardPerHour() external view returns (uint256) {
return _getRewardValue();
}
function setRewardPerHour(uint256 amount) public onlyOwner returns (bool) {
_setRewardValue(amount);
return true;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @notice Contract is a inheritable smart contract that will add a
* New modifier called onlyOwner available in the smart contract inherting it
*
* onlyOwner makes a function only callable from the Token owner
*
*/
contract Ownable {
// _owner is the owner of the Token
address private _owner;
/**
* Event OwnershipTransferred is used to log that a ownership change of the token has occured
*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* Modifier
* We create our own function modifier called onlyOwner, it will Require the current owner to be
* the same as msg.sender
*/
modifier onlyOwner() {
require(_owner == msg.sender, "Ownable: only owner can call this function");
// This _; is not a TYPO, It is important for the compiler;
_;
}
constructor() {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @notice owner() returns the currently assigned owner of the Token
*
*/
function owner() public view returns(address) {
return _owner;
}
/**
* @notice renounceOwnership will set the owner to zero address
* This will make the contract owner less, It will make ALL functions with
* onlyOwner no longer callable.
* There is no way of restoring the owner
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @notice transferOwnership will assign the {newOwner} as owner
*
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @notice _transferOwnership will assign the {newOwner} as owner
*
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @notice Stakeable is a contract who is ment to be inherited by other contract that wants Staking capabilities
*/
contract Stakeable {
/**
* @notice Constructor since this contract is not ment to be used without inheritance
* push once to stakeholders for it to work proplerly
*/
constructor() {
// This push is needed so we avoid index 0 causing bug of index-1
stakeholders.push();
//Se calcula el _rewardPerHour por (100*8760)/x
//Donde 100 son los tokens, 8760 es un año en horas y x es el APR deseado
_rewardPerHour = 109500;
}
uint256 private _rewardPerHour;
/**
* @notice
* A stake struct is used to represent the way we store stakes,
* A Stake will contain the users address, the amount staked and a timestamp,
* Since which is when the stake was made
*/
struct Stake {
address user;
uint256 amount;
uint256 since;
// This claimable field is new and used to tell how big of a reward is currently available
uint256 claimable;
}
/**
* @notice Stakeholder is a staker that has active stakes
*/
struct Stakeholder {
address user;
Stake[] address_stakes;
}
/**
* @notice
* StakingSummary is a struct that is used to contain all stakes performed by a certain account
*/
struct StakingSummary {
uint256 total_amount;
Stake[] stakes;
}
/**
* @notice
* This is a array where we store all Stakes that are performed on the Contract
* The stakes for each address are stored at a certain index, the index can be found using the stakes mapping
*/
Stakeholder[] internal stakeholders;
/**
* @notice
* stakes is used to keep track of the INDEX for the stakers in the stakes array
*/
mapping(address => uint256) internal stakes;
/**
* @notice Staked event is triggered whenever a user stakes tokens, address is indexed to make it filterable
*/
event Staked(
address indexed user,
uint256 amount,
uint256 index,
uint256 timestamp
);
/**
* @notice
_rewardPerHour is 1000 because it is used to represent 0.001, since we only use integer numbers
This will give users 0.1% reward for each staked token / H
*/
/**
* @notice _addStakeholder takes care of adding a stakeholder to the stakeholders array
*/
function _addStakeholder(address staker) internal returns (uint256) {
// Push a empty item to the Array to make space for our new stakeholder
stakeholders.push();
// Calculate the index of the last item in the array by Len-1
uint256 userIndex = stakeholders.length - 1;
// Assign the address to the new index
stakeholders[userIndex].user = staker;
// Add index to the stakeHolders
stakes[staker] = userIndex;
return userIndex;
}
/**
* @notice
* _Stake is used to make a stake for an sender. It will remove the amount staked from the stakers account and place those tokens inside a stake container
* StakeID
*/
function _stake(uint256 _amount) internal {
// Simple check so that user does not stake 0
require(_amount > 0, "Cannot stake nothing");
// Mappings in solidity creates all values, but empty, so we can just check the address
uint256 index = stakes[msg.sender];
// block.timestamp = timestamp of the current block in seconds since the epoch
uint256 timestamp = block.timestamp;
// See if the staker already has a staked index or if its the first time
if (index == 0) {
// This stakeholder stakes for the first time
// We need to add him to the stakeHolders and also map it into the Index of the stakes
// The index returned will be the index of the stakeholder in the stakeholders array
index = _addStakeholder(msg.sender);
}
// Use the index to push a new Stake
// push a newly created Stake with the current block timestamp.
stakeholders[index].address_stakes.push(
Stake(msg.sender, _amount, timestamp, 0)
);
// Emit an event that the stake has occured
emit Staked(msg.sender, _amount, index, timestamp);
}
/**
* @notice
* calculateStakeReward is used to calculate how much a user should be rewarded for their stakes
* and the duration the stake has been active
*/
function calculateStakeReward(Stake memory _current_stake)
internal
view
returns (uint256)
{
// First calculate how long the stake has been active
// Use current seconds since epoch - the seconds since epoch the stake was made
// The output will be duration in SECONDS ,
// We will reward the user 0.1% per Hour So thats 0.1% per 3600 seconds
// the alghoritm is seconds = block.timestamp - stake seconds (block.timestap - _stake.since)
// hours = Seconds / 3600 (seconds /3600) 3600 is an variable in Solidity names hours
// we then multiply each token by the hours staked , then divide by the _rewardPerHour rate
return
(((block.timestamp - _current_stake.since) / 1 hours) *
_current_stake.amount) / _rewardPerHour;
}
/**
* @notice
* withdrawStake takes in an amount and a index of the stake and will remove tokens from that stake
* Notice index of the stake is the users stake counter, starting at 0 for the first stake
* Will return the amount to MINT onto the acount
* Will also calculateStakeReward and reset timer
*/
function _withdrawStake(uint256 amount, uint256 index)
internal
returns (uint256)
{
// Grab user_index which is the index to use to grab the Stake[]
uint256 user_index = stakes[msg.sender];
Stake memory current_stake = stakeholders[user_index].address_stakes[
index
];
require(
current_stake.amount >= amount,
"Staking: Cannot withdraw more than you have staked"
);
// Calculate available Reward first before we start modifying data
uint256 reward = calculateStakeReward(current_stake);
// Remove by subtracting the money unstaked
current_stake.amount = current_stake.amount - amount;
// If stake is empty, 0, then remove it from the array of stakes
if (current_stake.amount == 0) {
delete stakeholders[user_index].address_stakes[index];
} else {
// If not empty then replace the value of it
stakeholders[user_index]
.address_stakes[index]
.amount = current_stake.amount;
// Reset timer of stake
stakeholders[user_index].address_stakes[index].since = block
.timestamp;
}
return amount + reward;
}
/**
* @notice
* hasStake is used to check if a account has stakes and the total amount along with all the seperate stakes
*/
function hasStake(address _staker)
public
view
returns (StakingSummary memory)
{
// totalStakeAmount is used to count total staked amount of the address
uint256 totalStakeAmount;
// Keep a summary in memory since we need to calculate this
StakingSummary memory summary = StakingSummary(
0,
stakeholders[stakes[_staker]].address_stakes
);
// Itterate all stakes and grab amount of stakes
for (uint256 s = 0; s < summary.stakes.length; s += 1) {
uint256 availableReward = calculateStakeReward(summary.stakes[s]);
summary.stakes[s].claimable = availableReward;
totalStakeAmount = totalStakeAmount + summary.stakes[s].amount;
}
// Assign calculate amount to summary
summary.total_amount = totalStakeAmount;
return summary;
}
function viewAll(address _staker) public view returns (uint256) {
uint256 totalAmount;
uint256 user_index = stakes[_staker];
StakingSummary memory summary = StakingSummary(
0,
stakeholders[stakes[_staker]].address_stakes
);
// Itterate all stakes and grab amount of stakes
for (uint256 s = 0; s < summary.stakes.length; s += 1) {
Stake memory current_stake = stakeholders[user_index]
.address_stakes[s];
uint256 availableReward = calculateStakeReward(current_stake);
totalAmount = current_stake.amount + availableReward;
}
return totalAmount;
}
function _withdrawAll() internal returns (uint256) {
uint256 totalWithdraw;
uint256 user_index = stakes[msg.sender];
StakingSummary memory summary = StakingSummary(
0,
stakeholders[stakes[msg.sender]].address_stakes
);
// Itterate all stakes and grab amount of stakes
for (uint256 s = 0; s < summary.stakes.length; s += 1) {
Stake memory current_stake = stakeholders[user_index]
.address_stakes[s];
uint256 availableReward = calculateStakeReward(current_stake);
totalWithdraw = current_stake.amount + availableReward;
delete stakeholders[user_index].address_stakes[s];
}
return totalWithdraw;
}
function _getRewardValue() internal view returns (uint256) {
return _rewardPerHour;
}
function _setRewardValue(uint256 amount) internal {
require(amount > 0, "Cannot be 0%");
_rewardPerHour = amount;
}
}