Skip to content

Commit a77a87b

Browse files
CodesenSysclaude
andcommitted
WIP snapshot: refactored core, unit tests, deploy script, and CLAUDE.md
- Restructured src into core/, interfaces/, tokens/ layers - Added BaseERC20, Constants, Errors, Modifiers contracts - Added CodesenSysToken with owner-gated mint and open burn - Added unit tests for Transfer, Allowance, MintBurn with BaseTest setup - Added Deploy script and CLAUDE.md - Invariant tests and fuzz suite pending completion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e65c1d8 commit a77a87b

12 files changed

Lines changed: 1219 additions & 70 deletions

File tree

CLAUDE.md

Lines changed: 445 additions & 0 deletions
Large diffs are not rendered by default.

script/Deploy.s.sol

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.4;
3-
import {Script} from "forge-std/Script.sol";
4-
import {CodesenSysToken} from "../src/tokens/CodesenSysToken.sol";
53

4+
import {Script, console} from "forge-std/Script.sol";
5+
import {CodesenSysToken} from "../src/tokens/CodesenSysToken.sol";
6+
import {Constants} from "../src/core/Constants.sol";
67

78
contract Deploy is Script {
8-
function run() external {
9-
// uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
10-
// vm.startBroadcast(vm.envUint("DEPLOYER_PRIVATE_KEY"));
11-
vm.startBroadcast();
12-
new CodesenSysToken(msg.sender, 1_000_000e18);
9+
function run() external returns (CodesenSysToken token) {
10+
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
11+
address owner = vm.envAddress("OWNER_ADDRESS");
12+
13+
vm.startBroadcast(deployerPrivateKey);
14+
token = new CodesenSysToken(owner, Constants.INITIAL_SUPPLY);
1315
vm.stopBroadcast();
16+
17+
console.log("Token deployed at :", address(token));
18+
console.log("Owner :", owner);
19+
console.log("Initial supply :", Constants.INITIAL_SUPPLY);
1420
}
1521
}
16-

src/core/Constants.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.4;
3+
library Constants {
4+
5+
// Token Metadata
6+
string internal constant TOKEN_NAME = "CodesenSys Token";
7+
string internal constant TOKEN_SYMBOL = "CSS";
8+
uint8 internal constant DECIMALS = 18;
9+
uint256 private constant _SCALAR = 10 ** uint256(DECIMALS);
10+
uint256 internal constant INITIAL_SUPPLY = 1_000_000 * _SCALAR;
11+
12+
/// @dev Hard cap of 1,000,000,000 CSS (1 Billion).
13+
uint256 internal constant MAX_SUPPLY = 1_000_000_000 * _SCALAR;
14+
}

src/core/Errors.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.4;
3+
4+
/// @title Errors
5+
/// @author CodesenSys
6+
/// @notice Centralised custom errors for the CodesenSys protocol.
7+
/// Custom errors save gas vs string reverts and give callers
8+
/// a typed selector to match against in tests.
39
library Errors {
10+
/// @dev Thrown when a zero address is supplied where one is not allowed.
411
error ZeroAddress();
12+
13+
/// @dev Thrown when a zero amount is supplied where one is not allowed.
514
error ZeroAmount();
15+
16+
/// @dev Thrown when msg.sender is not the authorised OWNER.
17+
error NotAuthorized();
18+
19+
/// @dev Thrown when a mint would push totalSupply past MAX_SUPPLY.
20+
error ExceedsCap();
621
}

src/core/Modifiers.sol

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.4;
2+
pragma solidity ^0.8.4;
3+
4+
import {Errors} from "./Errors.sol";
5+
6+
/// @title Modifiers
7+
/// @author CodesenSys
8+
/// @notice Abstract contract that owns the OWNER immutable and exposes the
9+
/// onlyOwner modifier. Inheriting contracts pass the owner address
10+
/// through the constructor — no storage slot is used (immutable).
11+
abstract contract Modifiers {
12+
13+
// State
14+
address public immutable owner;
15+
16+
// Constructor
17+
constructor(address _owner) {
18+
if (_owner == address(0)) revert Errors.ZeroAddress();
19+
owner = _owner;
20+
}
21+
22+
// Modifiers
23+
/// @dev Reverts with a custom error when the caller is not the OWNER.
24+
/// Custom errors are cheaper than string reverts (less calldata).
25+
modifier onlyOwner() {
26+
if (msg.sender != owner) revert Errors.NotAuthorized();
27+
_;
28+
}
29+
}

src/interfaces/IERC20.sol

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,72 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.4;
2+
pragma solidity ^0.8.4;
3+
4+
/// @title IERC20
5+
/// @author CodesenSys
6+
/// @notice Full EIP-20 interface plus the mint/burn extensions
7+
/// specific to CodesenSysToken.
8+
/// Solady's ERC20 already satisfies the core surface —
9+
/// this interface lets external contracts and off-chain
10+
/// tooling reference the complete ABI from a single file.
11+
interface IERC20 {
12+
13+
// =========================================================================
14+
// Events (EIP-20 required)
15+
// =========================================================================
16+
17+
/// @notice Emitted on every successful transfer, including mints (from == 0)
18+
/// and burns (to == 0).
19+
event Transfer(address indexed from, address indexed to, uint256 value);
20+
21+
/// @notice Emitted when an owner updates a spender's allowance via approve()
22+
/// or when a transferFrom() reduces it.
23+
event Approval(address indexed owner, address indexed spender, uint256 value);
24+
25+
// =========================================================================
26+
// EIP-20 Core
27+
// =========================================================================
28+
29+
/// @notice Human-readable name of the token (e.g. "CodesenSys Token").
30+
function name() external view returns (string memory);
31+
32+
/// @notice Short ticker symbol (e.g. "CSS").
33+
function symbol() external view returns (string memory);
34+
35+
/// @notice Number of decimal places used to represent token amounts.
36+
/// CSS uses 18, matching Ether convention.
37+
function decimals() external view returns (uint8);
38+
39+
/// @notice Total tokens currently in existence (minted minus burned).
40+
function totalSupply() external view returns (uint256);
41+
42+
/// @notice Token balance held by `account`.
43+
function balanceOf(address account) external view returns (uint256);
44+
45+
/// @notice Transfer `amount` from the caller to `to`.
46+
/// @return success True on success; implementations MUST revert on failure.
47+
function transfer(address to, uint256 amount) external returns (bool success);
48+
49+
/// @notice Remaining tokens that `spender` is allowed to move on behalf of `owner`.
50+
function allowance(address owner, address spender) external view returns (uint256);
51+
52+
/// @notice Approve `spender` to transfer up to `amount` from the caller's balance.
53+
/// @dev Setting to zero and then to the new value is the safe pattern for
54+
/// non-zero → non-zero updates on ERC-20 contracts that track old allowances.
55+
/// @return success True on success.
56+
function approve(address spender, uint256 amount) external returns (bool success);
57+
58+
/// @notice Move `amount` from `from` to `to` using the caller's allowance.
59+
/// @return success True on success.
60+
function transferFrom(address from, address to, uint256 amount) external returns (bool success);
61+
62+
// =========================================================================
63+
// Extensions — CodesenSysToken specific
64+
// =========================================================================
65+
66+
/// @notice Mint `amount` new tokens to `to`. Restricted to OWNER.
67+
/// @dev Reverts with Errors.ExceedsCap if totalSupply + amount > MAX_SUPPLY.
68+
function mint(address to, uint256 amount) external;
69+
70+
/// @notice Burn `amount` tokens from the caller's own balance.
71+
function burn(uint256 amount) external;
72+
}

src/tokens/CodesenSysToken.sol

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,48 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.4;
3+
34
import {BaseERC20} from "../core/BaseERC20.sol";
5+
import {Modifiers} from "../core/Modifiers.sol";
6+
import {Constants} from "../core/Constants.sol";
7+
import {Errors} from "../core/Errors.sol";
8+
9+
/// @title CodesenSysToken
10+
/// @author CodesenSys
11+
/// @notice ERC-20 token with owner-gated minting, open burning, and a hard 1 billion token supply cap.
12+
contract CodesenSysToken is BaseERC20, Modifiers {
413

5-
contract CodesenSysToken is BaseERC20 {
6-
address public immutable OWNER;
14+
// Constructor
715

8-
constructor(address _owner, uint256 initialSupply) {
9-
OWNER = _owner;
16+
/// @param _owner Receives the initial supply and mint rights.
17+
/// @param initialSupply Tokens minted at deployment (use Constants.INITIAL_SUPPLY).
18+
constructor(address _owner, uint256 initialSupply)
19+
Modifiers(_owner)
20+
{
1021
_mintInternal(_owner, initialSupply);
1122
}
1223

24+
// Metadata
25+
1326
function name() public pure override returns (string memory) {
14-
return "CodesenSys Token";
27+
return Constants.TOKEN_NAME;
1528
}
1629

1730
function symbol() public pure override returns (string memory) {
18-
return "CSS";
31+
return Constants.TOKEN_SYMBOL;
1932
}
2033

21-
function mint(address to, uint256 amount) external {
22-
if (msg.sender != OWNER) revert("NOT_AUTHORIZED");
34+
// Core logic
35+
36+
/// @notice Mint new tokens. Restricted to OWNER; enforces the 1B hard cap.
37+
/// @param to Recipient — must not be the zero address (checked in BaseERC20).
38+
/// @param amount Token quantity in wei — must be non-zero (checked in BaseERC20).
39+
function mint(address to, uint256 amount) external onlyOwner {
40+
if (totalSupply() + amount > Constants.MAX_SUPPLY) revert Errors.ExceedsCap();
2341
_mintInternal(to, amount);
2442
}
2543

44+
/// @notice Burn tokens from the caller's own balance.
45+
/// @param amount Token quantity in wei — must be non-zero (checked in BaseERC20).
2646
function burn(uint256 amount) external {
2747
_burnInternal(msg.sender, amount);
2848
}
Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,41 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.4;
2+
pragma solidity ^0.8.24;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
import {CodesenSysToken} from "../../src/tokens/CodesenSysToken.sol";
6+
7+
contract CodesenSysTokenInvariant is Test {
8+
CodesenSysToken public token;
9+
address public owner = 0x8C02Cadc32805F85037266206ce4FF347148bb98;
10+
11+
function setUp() public {
12+
token = new CodesenSysToken(owner, 1_000_000e18);
13+
}
14+
15+
function invariant_TotalSupplyEqualsBalances() public view {
16+
// This would need a handler to track all addresses
17+
// For now, simplified version
18+
assertEq(
19+
token.totalSupply(),
20+
token.balanceOf(owner) // + all other balances
21+
);
22+
}
23+
24+
// INVARIANT 2: Total supply never goes below zero
25+
function invariant_TotalSupplyNeverNegative() public view {
26+
assertGe(token.totalSupply(), 0);
27+
}
28+
29+
// INVARIANT 3: Owner balance never exceeds total supply
30+
function invariant_OwnerBalanceNeverExceedsTotalSupply() public view {
31+
assertLe(token.balanceOf(owner), token.totalSupply());
32+
}
33+
34+
// INVARIANT 4: Any user balance never exceeds total supply
35+
function invariant_NoBalanceExceedsTotalSupply() public view {
36+
// Would need handler to track all users
37+
assertLe(token.balanceOf(owner), token.totalSupply());
38+
}
39+
}
40+
41+

0 commit comments

Comments
 (0)