From 131eb991f3dfbfd12671d3a3f474a61577c993b9 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 13 Mar 2026 20:46:57 +0000 Subject: [PATCH 1/3] [#1] Initialize Foundry project with interfaces Set up Foundry project targeting Solc 0.8.28 with Base/Base Sepolia RPC endpoints. Add IMCV2_Bond interface (createToken, updateBondCreator, mint, burn, getReserveForToken, claimRoyalties) matching the deployed Mint Club V2 Bond at 0xc5a076...FAa27. Add minimal IERC20 interface. Add placeholder StoryFactory with BOND and PLOT_TOKEN immutables. Fixes #1 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test.yml | 38 +++++++++++++++++++ .gitignore | 14 +++++++ .gitmodules | 3 ++ README.md | 66 ++++++++++++++++++++++++++++++++ foundry.lock | 8 ++++ foundry.toml | 10 +++++ lib/forge-std | 1 + src/StoryFactory.sol | 17 +++++++++ src/interfaces/IERC20.sol | 11 ++++++ src/interfaces/IMCV2_Bond.sol | 71 +++++++++++++++++++++++++++++++++++ 10 files changed, 239 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 foundry.lock create mode 100644 foundry.toml create mode 160000 lib/forge-std create mode 100644 src/StoryFactory.sol create mode 100644 src/interfaces/IERC20.sol create mode 100644 src/interfaces/IMCV2_Bond.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b79c8d4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: CI + +permissions: {} + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: forge --version + + - name: Run Forge fmt + run: forge fmt --check + + - name: Run Forge build + run: forge build --sizes + + - name: Run Forge tests + run: forge test -vvv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..888d42d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/README.md b/README.md new file mode 100644 index 0000000..8817d6a --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..bc06b89 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.15.0", + "rev": "0844d7e1fc5e60d77b68e469bff60265f236c398" + } + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..c127727 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,10 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc = "0.8.28" +evm_version = "cancun" + +[rpc_endpoints] +base = "https://mainnet.base.org" +base_sepolia = "https://sepolia.base.org" diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..0844d7e --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 0844d7e1fc5e60d77b68e469bff60265f236c398 diff --git a/src/StoryFactory.sol b/src/StoryFactory.sol new file mode 100644 index 0000000..a2ee865 --- /dev/null +++ b/src/StoryFactory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {IMCV2_Bond} from "./interfaces/IMCV2_Bond.sol"; +import {IERC20} from "./interfaces/IERC20.sol"; + +/// @title StoryFactory — PlotLink storyline and plot management +/// @notice Placeholder — full implementation in subsequent tickets +contract StoryFactory { + IMCV2_Bond public immutable BOND; + IERC20 public immutable PLOT_TOKEN; + + constructor(address _bond, address _plotToken) { + BOND = IMCV2_Bond(_bond); + PLOT_TOKEN = IERC20(_plotToken); + } +} diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol new file mode 100644 index 0000000..172505e --- /dev/null +++ b/src/interfaces/IERC20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +/// @title IERC20 — Minimal ERC-20 interface for StoryFactory interactions +interface IERC20 { + function approve(address spender, uint256 amount) external returns (bool); + function transfer(address to, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); +} diff --git a/src/interfaces/IMCV2_Bond.sol b/src/interfaces/IMCV2_Bond.sol new file mode 100644 index 0000000..e44bd5f --- /dev/null +++ b/src/interfaces/IMCV2_Bond.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +/// @title IMCV2_Bond — Interface for Mint Club V2 Bond contract on Base +/// @dev Deployed at 0xc5a076cad94176c2996B32d8466Be1cE757FAa27 +/// @dev Reference: https://github.com/nicedoc/mintclub-v2 +interface IMCV2_Bond { + /// @notice Create a new token with bonding curve parameters + /// @param name Token name + /// @param symbol Token symbol + /// @param reserveToken Address of the reserve token (e.g. $PLOT) + /// @param maxSupply Maximum supply of the new token + /// @param stepRanges Array of supply thresholds for each price step + /// @param stepPrices Array of prices at each step + /// @param creatorAddress Address that receives royalties + /// @param mintRoyalty Mint royalty in basis points (e.g. 500 = 5%) + /// @param burnRoyalty Burn royalty in basis points + function createToken( + string calldata name, + string calldata symbol, + address reserveToken, + uint256 maxSupply, + uint256[] calldata stepRanges, + uint256[] calldata stepPrices, + address creatorAddress, + uint16 mintRoyalty, + uint16 burnRoyalty + ) external returns (address tokenAddress); + + /// @notice Transfer the creator role (royalty recipient) for a token + /// @param token Address of the token + /// @param newCreator New creator address + function updateBondCreator(address token, address newCreator) external; + + /// @notice Mint tokens on the bonding curve + /// @param token Address of the token to mint + /// @param tokensToMint Amount of tokens to mint + /// @param maxReserveAmount Maximum reserve token amount willing to spend (slippage) + /// @param receiver Address to receive the minted tokens + function mint( + address token, + uint256 tokensToMint, + uint256 maxReserveAmount, + address receiver + ) external; + + /// @notice Burn tokens on the bonding curve + /// @param token Address of the token to burn + /// @param tokensToBurn Amount of tokens to burn + /// @param minRefund Minimum reserve token refund (slippage) + /// @param receiver Address to receive the refund + function burn( + address token, + uint256 tokensToBurn, + uint256 minRefund, + address receiver + ) external; + + /// @notice Get the reserve amount required to mint a given number of tokens + /// @param token Address of the token + /// @param tokensToMint Amount of tokens to mint + /// @return reserveAmount Required reserve token amount + function getReserveForToken( + address token, + uint256 tokensToMint + ) external view returns (uint256 reserveAmount); + + /// @notice Claim accumulated royalties for a token + /// @param token Address of the token + function claimRoyalties(address token) external; +} From d94750303486dac1f4bf1ba103873dbdb99daaa8 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 13 Mar 2026 20:49:08 +0000 Subject: [PATCH 2/3] [#1] Fix forge fmt formatting in IMCV2_Bond interface Co-Authored-By: Claude Opus 4.6 (1M context) --- src/interfaces/IMCV2_Bond.sol | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/interfaces/IMCV2_Bond.sol b/src/interfaces/IMCV2_Bond.sol index e44bd5f..4d1a255 100644 --- a/src/interfaces/IMCV2_Bond.sol +++ b/src/interfaces/IMCV2_Bond.sol @@ -37,33 +37,20 @@ interface IMCV2_Bond { /// @param tokensToMint Amount of tokens to mint /// @param maxReserveAmount Maximum reserve token amount willing to spend (slippage) /// @param receiver Address to receive the minted tokens - function mint( - address token, - uint256 tokensToMint, - uint256 maxReserveAmount, - address receiver - ) external; + function mint(address token, uint256 tokensToMint, uint256 maxReserveAmount, address receiver) external; /// @notice Burn tokens on the bonding curve /// @param token Address of the token to burn /// @param tokensToBurn Amount of tokens to burn /// @param minRefund Minimum reserve token refund (slippage) /// @param receiver Address to receive the refund - function burn( - address token, - uint256 tokensToBurn, - uint256 minRefund, - address receiver - ) external; + function burn(address token, uint256 tokensToBurn, uint256 minRefund, address receiver) external; /// @notice Get the reserve amount required to mint a given number of tokens /// @param token Address of the token /// @param tokensToMint Amount of tokens to mint /// @return reserveAmount Required reserve token amount - function getReserveForToken( - address token, - uint256 tokensToMint - ) external view returns (uint256 reserveAmount); + function getReserveForToken(address token, uint256 tokensToMint) external view returns (uint256 reserveAmount); /// @notice Claim accumulated royalties for a token /// @param token Address of the token From 46b1c0e7f11b87b1d6af494d3f37278e47751ee6 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 13 Mar 2026 20:53:20 +0000 Subject: [PATCH 3/3] [#1] Fix IMCV2_Bond interface to match deployed contract Update interface to match actual Mint Club V2 Bond (Steemhunt): - createToken uses (TokenParams, BondParams) struct params - mint/burn return uint256 - getReserveForToken returns (reserveAmount, royalty) - Add getRefundForTokens - claimRoyalties takes reserveToken address (not token) - Add TokenParams and BondParams struct definitions Co-Authored-By: Claude Opus 4.6 (1M context) --- src/interfaces/IMCV2_Bond.sol | 85 +++++++++++++++++------------------ 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/interfaces/IMCV2_Bond.sol b/src/interfaces/IMCV2_Bond.sol index 4d1a255..6875e24 100644 --- a/src/interfaces/IMCV2_Bond.sol +++ b/src/interfaces/IMCV2_Bond.sol @@ -3,56 +3,51 @@ pragma solidity ^0.8.28; /// @title IMCV2_Bond — Interface for Mint Club V2 Bond contract on Base /// @dev Deployed at 0xc5a076cad94176c2996B32d8466Be1cE757FAa27 -/// @dev Reference: https://github.com/nicedoc/mintclub-v2 +/// @dev Source: github.com/Steemhunt/mint.club-v2-contract + +struct TokenParams { + string name; + string symbol; +} + +struct BondParams { + uint16 mintRoyalty; + uint16 burnRoyalty; + address reserveToken; + uint128 maxSupply; + uint128[] stepRanges; + uint128[] stepPrices; +} + interface IMCV2_Bond { - /// @notice Create a new token with bonding curve parameters - /// @param name Token name - /// @param symbol Token symbol - /// @param reserveToken Address of the reserve token (e.g. $PLOT) - /// @param maxSupply Maximum supply of the new token - /// @param stepRanges Array of supply thresholds for each price step - /// @param stepPrices Array of prices at each step - /// @param creatorAddress Address that receives royalties - /// @param mintRoyalty Mint royalty in basis points (e.g. 500 = 5%) - /// @param burnRoyalty Burn royalty in basis points - function createToken( - string calldata name, - string calldata symbol, - address reserveToken, - uint256 maxSupply, - uint256[] calldata stepRanges, - uint256[] calldata stepPrices, - address creatorAddress, - uint16 mintRoyalty, - uint16 burnRoyalty - ) external returns (address tokenAddress); + /// @notice Create a new ERC-20 token with bonding curve + function createToken(TokenParams calldata tp, BondParams calldata bp) external payable returns (address); /// @notice Transfer the creator role (royalty recipient) for a token - /// @param token Address of the token - /// @param newCreator New creator address - function updateBondCreator(address token, address newCreator) external; + function updateBondCreator(address token, address creator) external; /// @notice Mint tokens on the bonding curve - /// @param token Address of the token to mint - /// @param tokensToMint Amount of tokens to mint - /// @param maxReserveAmount Maximum reserve token amount willing to spend (slippage) - /// @param receiver Address to receive the minted tokens - function mint(address token, uint256 tokensToMint, uint256 maxReserveAmount, address receiver) external; + /// @return reserveAmount Amount of reserve token spent + function mint(address token, uint256 tokensToMint, uint256 maxReserveAmount, address receiver) + external + returns (uint256); /// @notice Burn tokens on the bonding curve - /// @param token Address of the token to burn - /// @param tokensToBurn Amount of tokens to burn - /// @param minRefund Minimum reserve token refund (slippage) - /// @param receiver Address to receive the refund - function burn(address token, uint256 tokensToBurn, uint256 minRefund, address receiver) external; - - /// @notice Get the reserve amount required to mint a given number of tokens - /// @param token Address of the token - /// @param tokensToMint Amount of tokens to mint - /// @return reserveAmount Required reserve token amount - function getReserveForToken(address token, uint256 tokensToMint) external view returns (uint256 reserveAmount); - - /// @notice Claim accumulated royalties for a token - /// @param token Address of the token - function claimRoyalties(address token) external; + /// @return refundAmount Amount of reserve token refunded + function burn(address token, uint256 tokensToBurn, uint256 minRefund, address receiver) external returns (uint256); + + /// @notice Get the reserve amount required to mint tokens + function getReserveForToken(address token, uint256 tokensToMint) + external + view + returns (uint256 reserveAmount, uint256 royalty); + + /// @notice Get the refund for burning tokens + function getRefundForTokens(address token, uint256 tokensToBurn) + external + view + returns (uint256 refundAmount, uint256 royalty); + + /// @notice Claim accumulated royalties (inherited from MCV2_Royalty) + function claimRoyalties(address reserveToken) external; }