Skip to content

Commit

Permalink
Merge branch 'main' into gauges
Browse files Browse the repository at this point in the history
  • Loading branch information
franzns committed Dec 14, 2024
2 parents 83ef44c + fd024cf commit fba087b
Show file tree
Hide file tree
Showing 7 changed files with 595 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"semi": false,
"overrides": [
{
"files": "*.sol",
"options": {
"bracketSpacing": false,
"printWidth": 145,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"explicitTypes": "always"
}
}
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.formatOnSave": true,
"[solidity]": {
"editor.defaultFormatter": "JuanBlanco.solidity"
},
"solidity.formatter": "forge"
}
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ extra_output = ["storageLayout"]
evm_version = 'shanghai'

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

[fuzz]
runs = 10000
98 changes: 98 additions & 0 deletions src/token/Beets.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";

contract Beets is ERC20, ERC20Permit, Ownable {
uint256 public constant YEAR_IN_SECONDS = 365 days;
// 10% per year is the hardcoded max inflation rate, as defined in BIP-77
uint256 public constant MAX_INFLATION_PER_YEAR = 1e17;

// The initial start timestamp is defined on deployment. This is the start time of the current year
// for which the minting cap is calculated.
uint256 public startTimestampCurrentYear;

// The amount of tokens we've minted so far for the current year
uint256 public amountMintedCurrentYear;

// The max amount of beets that can be minted for the current year. At the start of the year, we take the current
// total supply and calculate the max amount of beets that can be minted for the current year as 10% of the
// current total supply.
uint256 public maxAmountMintableCurrentYear;

error MintAmountTooHigh(uint256 remainingMintable);
error CurrentYearHasNotEnded();
error CurrentYearEnded();
error InitialSupplyIsZero();
error InititalMintTargetIsZero();
error OwnerIsZero();

constructor(uint256 _initialSupply, address _initialMintTarget, address _owner)
ERC20("Beets", "BEETS")
ERC20Permit("Beets")
Ownable(msg.sender)
{
require(_initialSupply > 0, InitialSupplyIsZero());
require(_initialMintTarget != address(0), InititalMintTargetIsZero());
require(_owner != address(0), OwnerIsZero());

_mint(_initialMintTarget, _initialSupply);

// The current year starts at the deployment timestamp
startTimestampCurrentYear = block.timestamp;

amountMintedCurrentYear = 0;

maxAmountMintableCurrentYear = (totalSupply() * MAX_INFLATION_PER_YEAR) / 1 ether;

transferOwnership(_owner);
}

function mint(address to, uint256 amount) public onlyOwner {
if (block.timestamp > getEndTimestampCurrentYear()) {
// The current year has ended, a call to incrementYear() is required before minting more tokens
// In the instance that several years have passed, we ensure that no tokens are minted for previous years
revert CurrentYearEnded();
}

amountMintedCurrentYear += amount;

if (amountMintedCurrentYear > maxAmountMintableCurrentYear) {
uint256 remainingMintable = maxAmountMintableCurrentYear - (amountMintedCurrentYear - amount);

revert MintAmountTooHigh(remainingMintable);
}

_mint(to, amount);
}

/**
* @notice Increments the current year by one. Must be called before minting more tokens once the current year
* has ended.
* @dev In the instance that several years have passed, this function may need to be called multiple times.
*/
function incrementYear() public onlyOwner {
if (block.timestamp <= getEndTimestampCurrentYear()) {
revert CurrentYearHasNotEnded();
}

// increment the current year by one
startTimestampCurrentYear += YEAR_IN_SECONDS;

// reset the amount minted for the current year
amountMintedCurrentYear = 0;

// the max amount of beets that can be minted for the current year
maxAmountMintableCurrentYear = (totalSupply() * MAX_INFLATION_PER_YEAR) / 1 ether;
}

/**
* @notice Calculates the end timestamp for the current year.
* @return The end timestamp for the current year.
*/
function getEndTimestampCurrentYear() public view returns (uint256) {
return startTimestampCurrentYear + YEAR_IN_SECONDS;
}
}
69 changes: 69 additions & 0 deletions src/token/SonicBeetsMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "openzeppelin-contracts/token/ERC20/IERC20.sol";

contract SonicBeetsMigrator {
IERC20 public immutable OPERABEETS;
IERC20 public immutable SONICBEETS;
address public immutable TREASURY;

bool public sonicToOperaEnabled = false;
bool public operaToSonicEnabled = false;

address public admin;

error UserBalanceInsufficient();
error MigratorBalanceInsufficient();
error MigrationDisabled();
error NotAdmin();

constructor(IERC20 _OPERABEETS, IERC20 _SONICBEETS, address _TREASURY) {
OPERABEETS = _OPERABEETS;
SONICBEETS = _SONICBEETS;
TREASURY = _TREASURY;
admin = msg.sender;
}

function exchangeOperaToSonic(uint256 amount) public {
require(operaToSonicEnabled, MigrationDisabled());
require(OPERABEETS.balanceOf(msg.sender) >= amount, UserBalanceInsufficient());
require(SONICBEETS.balanceOf(address(this)) >= amount, MigratorBalanceInsufficient());
OPERABEETS.transferFrom(msg.sender, address(this), amount);
SONICBEETS.transfer(msg.sender, amount);
}

function exchangeSonicToOpera(uint256 amount) public {
require(sonicToOperaEnabled, MigrationDisabled());
require(SONICBEETS.balanceOf(msg.sender) >= amount, UserBalanceInsufficient());
require(OPERABEETS.balanceOf(address(this)) >= amount, MigratorBalanceInsufficient());
SONICBEETS.transferFrom(msg.sender, address(this), amount);
OPERABEETS.transfer(msg.sender, amount);
}

function setAdmin(address _admin) public {
require(msg.sender == admin, NotAdmin());
admin = _admin;
}

function enableOperaToSonic(bool _toggle) external {
require(msg.sender == admin, NotAdmin());
operaToSonicEnabled = _toggle;
}

function enableSonicToOpera(bool _toggle) external {
require(msg.sender == admin, NotAdmin());
sonicToOperaEnabled = _toggle;
}

function withdrawOperaBeets() public {
require(msg.sender == admin, NotAdmin());
OPERABEETS.transfer(TREASURY, OPERABEETS.balanceOf(address(this)));
}

function withdrawSonicBeets() public {
require(msg.sender == admin, NotAdmin());
SONICBEETS.transfer(TREASURY, SONICBEETS.balanceOf(address(this)));
}
}
Loading

0 comments on commit fba087b

Please sign in to comment.