Description:
ERC20 token contract with Factory capabilities. Standard implementation for fungible tokens on Ethereum.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"cyclx.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.24;\r
\r
interface IERC20 {\r
function totalSupply() external view returns (uint256);\r
function balanceOf(address account) external view returns (uint256);\r
function transfer(address to, uint256 value) external returns (bool);\r
function allowance(address owner, address spender) external view returns (uint256);\r
function approve(address spender, uint256 value) external returns (bool);\r
function transferFrom(address from, address to, uint256 value) external returns (bool);\r
event Transfer(address indexed from, address indexed to, uint256 value);\r
event Approval(address indexed owner, address indexed spender, uint256 value);\r
}\r
\r
library SafeERC20 {\r
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\r
(bool ok, bytes memory data) = address(token).call(\r
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)\r
);\r
require(ok && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");\r
}\r
\r
function safeTransfer(IERC20 token, address to, uint256 value) internal {\r
(bool ok, bytes memory data) = address(token).call(\r
abi.encodeWithSelector(token.transfer.selector, to, value)\r
);\r
require(ok && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");\r
}\r
}\r
\r
contract VersionToken is IERC20 {\r
string public name;\r
string public symbol;\r
uint8 public constant decimals = 18;\r
\r
uint256 public override totalSupply;\r
\r
mapping(address => uint256) public override balanceOf;\r
mapping(address => mapping(address => uint256)) public override allowance;\r
\r
bytes32 public immutable DOMAIN_SEPARATOR;\r
bytes32 public constant PERMIT_TYPEHASH = 0xd505accf1f3f3d6c6b379bdc8a2f8f2a78fef8a6a6b7b0b5b2c9f2d2f9a6e46a;\r
mapping(address => uint256) public nonces;\r
\r
address public immutable engine;\r
uint256 public immutable versionNumber;\r
\r
constructor(string memory _name, string memory _symbol, uint256 _versionNumber, address _engine) {\r
name = _name;\r
symbol = _symbol;\r
versionNumber = _versionNumber;\r
engine = _engine;\r
\r
uint256 chainId;\r
assembly { chainId := chainid() }\r
DOMAIN_SEPARATOR = keccak256(\r
abi.encode(\r
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),\r
keccak256(bytes(_name)),\r
keccak256(bytes("1")),\r
chainId,\r
address(this)\r
)\r
);\r
}\r
\r
modifier onlyEngine() {\r
require(msg.sender == engine, "ONLY_ENGINE");\r
_;\r
}\r
\r
function approve(address spender, uint256 value) external override returns (bool) {\r
allowance[msg.sender][spender] = value;\r
emit Approval(msg.sender, spender, value);\r
return true;\r
}\r
\r
function transfer(address to, uint256 value) external override returns (bool) {\r
_transfer(msg.sender, to, value);\r
return true;\r
}\r
\r
function transferFrom(address from, address to, uint256 value) external override returns (bool) {\r
uint256 allowed = allowance[from][msg.sender];\r
if (allowed != type(uint256).max) {\r
require(allowed >= value, "ALLOWANCE");\r
allowance[from][msg.sender] = allowed - value;\r
emit Approval(from, msg.sender, allowance[from][msg.sender]);\r
}\r
_transfer(from, to, value);\r
return true;\r
}\r
\r
function _transfer(address from, address to, uint256 value) internal {\r
require(to != address(0), "TO_ZERO");\r
uint256 bal = balanceOf[from];\r
require(bal >= value, "BALANCE");\r
unchecked { balanceOf[from] = bal - value; }\r
balanceOf[to] += value;\r
emit Transfer(from, to, value);\r
}\r
\r
function _mint(address to, uint256 value) internal {\r
require(to != address(0), "MINT_TO_ZERO");\r
totalSupply += value;\r
balanceOf[to] += value;\r
emit Transfer(address(0), to, value);\r
}\r
\r
function mintFromEngine(address to, uint256 value) external onlyEngine {\r
_mint(to, value);\r
}\r
\r
function permit(\r
address owner,\r
address spender,\r
uint256 value,\r
uint256 deadline,\r
uint8 v,\r
bytes32 r,\r
bytes32 s\r
) external {\r
require(block.timestamp <= deadline, "PERMIT_DEADLINE");\r
bytes32 digest = keccak256(\r
abi.encodePacked(\r
"\x19\x01",\r
DOMAIN_SEPARATOR,\r
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\r
)\r
);\r
address recovered = ecrecover(digest, v, r, s);\r
require(recovered != address(0) && recovered == owner, "BAD_SIG");\r
allowance[owner][spender] = value;\r
emit Approval(owner, spender, value);\r
}\r
}\r
\r
contract CyclicTokenEngine {\r
using SafeERC20 for IERC20;\r
\r
uint256 public constant REST_LEN = 30 days;\r
uint256 public constant STAKE_LEN = 2 days;\r
uint256 public constant CLAIM_LEN = 2 days;\r
uint256 public constant CYCLE_LEN = REST_LEN + STAKE_LEN + CLAIM_LEN;\r
\r
enum Phase { REST, STAKE, CLAIM }\r
\r
VersionToken public currentToken;\r
VersionToken public nextToken;\r
uint256 public currentVersion;\r
uint64 public cycleIndex;\r
uint64 public cycleStart;\r
\r
uint256 private _entered;\r
\r
struct StakeInfo {\r
uint256 amount;\r
bool claimed;\r
uint64 cycle;\r
}\r
mapping(address => StakeInfo) public stakes;\r
\r
event Staked(address indexed user, uint256 amount, uint64 indexed cycle);\r
event Claimed(address indexed user, uint256 amount, uint64 indexed cycle, address indexed nextVersion);\r
event NextVersionDeployed(address indexed token, uint256 indexed versionNumber, string name, string symbol);\r
event RolledToNextCycle(uint64 newCycleIndex, address indexed newCurrentToken);\r
event Synced(uint64 indexed cycleIndex, address indexed currentToken, uint8 phaseAfter);\r
\r
modifier nonReentrant() {\r
require(_entered == 0, "REENTRANCY");\r
_entered = 1;\r
_;\r
_entered = 0;\r
}\r
\r
constructor(string memory baseName, string memory baseSymbol, uint256 v1InitialSupply, address v1Receiver, uint64 firstCycleStartTimestamp) {\r
require(v1Receiver != address(0), "RECEIVER_ZERO");\r
require(firstCycleStartTimestamp > 0, "START_ZERO");\r
\r
string memory nameV1 = string(abi.encodePacked(baseName, "v1"));\r
string memory symbolV1 = string(abi.encodePacked(baseSymbol, "v1"));\r
VersionToken v1 = new VersionToken(nameV1, symbolV1, 1, address(this));\r
v1.mintFromEngine(v1Receiver, v1InitialSupply);\r
\r
currentToken = v1;\r
currentVersion = 1;\r
cycleIndex = 1;\r
cycleStart = firstCycleStartTimestamp;\r
\r
emit NextVersionDeployed(address(v1), 1, nameV1, symbolV1);\r
}\r
\r
function nowTs() public view returns (uint64) { return uint64(block.timestamp); }\r
\r
function phaseAt(uint64 ts) public view returns (Phase) {\r
uint256 d = ts - cycleStart;\r
if (d < REST_LEN) return Phase.REST;\r
if (d < REST_LEN + STAKE_LEN) return Phase.STAKE;\r
if (d < CYCLE_LEN) return Phase.CLAIM;\r
return Phase.REST;\r
}\r
\r
function currentPhase() public view returns (Phase) { return phaseAt(nowTs()); }\r
\r
function stake(uint256 amount) external nonReentrant {\r
_stakeInternal(msg.sender, amount);\r
}\r
\r
function stakeWithPermit(\r
uint256 amount,\r
uint256 deadline,\r
uint8 v,\r
bytes32 r,\r
bytes32 s\r
) external nonReentrant {\r
_rollIfNeeded();\r
require(currentPhase() == Phase.STAKE, "NOT_STAKE_WINDOW");\r
require(amount > 0, "ZERO_AMOUNT");\r
currentToken.permit(msg.sender, address(this), amount, deadline, v, r, s);\r
_afterPermitStake(msg.sender, amount);\r
}\r
\r
function _stakeInternal(address user, uint256 amount) internal {\r
_rollIfNeeded();\r
require(currentPhase() == Phase.STAKE, "NOT_STAKE_WINDOW");\r
require(amount > 0, "ZERO_AMOUNT");\r
\r
StakeInfo storage s = stakes[user];\r
if (s.cycle != cycleIndex) {\r
s.amount = 0; s.claimed = false; s.cycle = cycleIndex;\r
}\r
\r
IERC20(address(currentToken)).safeTransferFrom(user, address(this), amount);\r
s.amount += amount;\r
emit Staked(user, amount, cycleIndex);\r
}\r
\r
function _afterPermitStake(address user, uint256 amount) internal {\r
StakeInfo storage s = stakes[user];\r
if (s.cycle != cycleIndex) {\r
s.amount = 0; s.claimed = false; s.cycle = cycleIndex;\r
}\r
IERC20(address(currentToken)).safeTransferFrom(user, address(this), amount);\r
s.amount += amount;\r
emit Staked(user, amount, cycleIndex);\r
}\r
\r
function claim() external nonReentrant {\r
_rollIfNeeded();\r
require(currentPhase() == Phase.CLAIM, "NOT_CLAIM_WINDOW");\r
\r
StakeInfo storage s = stakes[msg.sender];\r
require(s.cycle == cycleIndex && !s.claimed, "NO_STAKE_OR_ALREADY");\r
uint256 amt = s.amount;\r
require(amt > 0, "ZERO_STAKE");\r
\r
if (address(nextToken) == address(0)) {\r
_deployNextVersion();\r
}\r
\r
IERC20(address(currentToken)).safeTransfer(msg.sender, amt);\r
nextToken.mintFromEngine(msg.sender, amt);\r
s.claimed = true;\r
s.amount = 0;\r
\r
emit Claimed(msg.sender, amt, cycleIndex, address(nextToken));\r
}\r
\r
function sync() external nonReentrant {\r
_rollIfNeeded();\r
emit Synced(cycleIndex, address(currentToken), uint8(currentPhase()));\r
}\r
\r
function syncAndInfo() external nonReentrant returns (\r
Phase phase,\r
uint64 cycle,\r
uint64 start,\r
uint64 restEnd,\r
uint64 stakeEnd,\r
uint64 claimEnd\r
) {\r
_rollIfNeeded();\r
uint64 ts = nowTs();\r
phase = phaseAt(ts);\r
cycle = cycleIndex;\r
start = cycleStart;\r
restEnd = start + uint64(REST_LEN);\r
stakeEnd = restEnd + uint64(STAKE_LEN);\r
claimEnd = stakeEnd + uint64(CLAIM_LEN);\r
emit Synced(cycleIndex, address(currentToken), uint8(phase));\r
}\r
\r
function phaseInfo() external view returns (Phase phase, uint64 cycle, uint64 start, uint64 restEnd, uint64 stakeEnd, uint64 claimEnd) {\r
uint64 ts = nowTs();\r
phase = phaseAt(ts);\r
cycle = cycleIndex;\r
start = cycleStart;\r
restEnd = start + uint64(REST_LEN);\r
stakeEnd = restEnd + uint64(STAKE_LEN);\r
claimEnd = stakeEnd + uint64(CLAIM_LEN);\r
}\r
\r
function _deployNextVersion() internal {\r
uint256 vNext = currentVersion + 1;\r
string memory baseName = _stripSuffix(currentToken.name());\r
string memory baseSymbol = _stripSuffix(currentToken.symbol());\r
string memory nameNext = string(abi.encodePacked(baseName, _toV(vNext)));\r
string memory symbolNext = string(abi.encodePacked(baseSymbol, _toV(vNext)));\r
VersionToken v = new VersionToken(nameNext, symbolNext, vNext, address(this));\r
nextToken = v;\r
emit NextVersionDeployed(address(v), vNext, nameNext, symbolNext);\r
}\r
\r
function _rollIfNeeded() internal {\r
uint64 ts = nowTs();\r
while (ts >= cycleStart + uint64(CYCLE_LEN)) {\r
if (address(nextToken) != address(0)) {\r
currentToken = nextToken;\r
currentVersion += 1;\r
nextToken = VersionToken(address(0));\r
} else {\r
_deployNextVersion();\r
currentToken = nextToken;\r
currentVersion += 1;\r
nextToken = VersionToken(address(0));\r
}\r
cycleIndex += 1;\r
cycleStart += uint64(CYCLE_LEN);\r
emit RolledToNextCycle(cycleIndex, address(currentToken));\r
}\r
}\r
\r
function _stripSuffix(string memory s) internal pure returns (string memory) {\r
bytes memory b = bytes(s);\r
if (b.length == 0) return s;\r
uint256 i = b.length;\r
while (i > 0 && _isDigit(b[i-1])) { i--; }\r
if (i > 0 && b[i-1] == bytes1("v")) {\r
bytes memory out = new bytes(i-1);\r
for (uint256 j = 0; j < i-1; j++) out[j] = b[j];\r
return string(out);\r
}\r
return s;\r
}\r
\r
function _isDigit(bytes1 c) private pure returns (bool) {\r
return (c >= 0x30 && c <= 0x39);\r
}\r
\r
function _toV(uint256 n) private pure returns (string memory) {\r
return string(abi.encodePacked("v", _toString(n)));\r
}\r
\r
function _toString(uint256 v) private pure returns (string memory) {\r
if (v == 0) return "0";\r
uint256 tmp = v; uint256 len;\r
while (tmp != 0) { len++; tmp /= 10; }\r
bytes memory b = new bytes(len);\r
while (v != 0) { b[--len] = bytes1(uint8(48 + v % 10)); v /= 10; }\r
return string(b);\r
}\r
}"
}
},
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": []
}
}}
Submitted on: 2025-10-31 19:00:04
Comments
Log in to comment.
No comments yet.