Lock membership defaults to USDC 100 and add ERC20 settlement tests
This commit is contained in:
parent
0718198924
commit
4f7977c3fd
@ -4,9 +4,9 @@ BASE_MAINNET_RPC_URL=https://mainnet.base.org
|
||||
|
||||
TREASURY_WALLET=0x0000000000000000000000000000000000000000
|
||||
|
||||
# Use zero address for native ETH pricing.
|
||||
MINT_CURRENCY_ADDRESS=0x0000000000000000000000000000000000000000
|
||||
MINT_AMOUNT_ATOMIC=5000000000000000
|
||||
# Base Sepolia USDC by default (override per network as needed).
|
||||
MINT_CURRENCY_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCf7e
|
||||
MINT_AMOUNT_ATOMIC=100000000
|
||||
|
||||
# Optional: write deployment metadata JSON (address, tx hash, params).
|
||||
# DEPLOY_OUTPUT_PATH=./deploy/membership-deploy.sepolia.json
|
||||
|
||||
@ -17,7 +17,7 @@ Features:
|
||||
|
||||
1. One-time soulbound human token mint.
|
||||
2. Sponsor mint support (`mintMembership(recipient)` can be paid by inviter/company wallet).
|
||||
3. Owner-configurable flat mint price (`updateMintPrice`), launch default can stay flat `$5` equivalent.
|
||||
3. Owner-configurable flat mint price (`updateMintPrice`), launch default is fixed `100 USDC` (6 decimals).
|
||||
4. Membership status lifecycle (`ACTIVE/SUSPENDED/REVOKED`) for runtime gates.
|
||||
5. Treasury address control for settlement routing.
|
||||
|
||||
@ -40,7 +40,7 @@ Copy `.env.example` values into your shell/session before deploy:
|
||||
1. `DEPLOYER_PRIVATE_KEY`
|
||||
2. `BASE_SEPOLIA_RPC_URL` / `BASE_MAINNET_RPC_URL`
|
||||
3. `TREASURY_WALLET`
|
||||
4. `MINT_CURRENCY_ADDRESS` (`0x000...000` for ETH)
|
||||
4. `MINT_CURRENCY_ADDRESS` (USDC token contract on target chain)
|
||||
5. `MINT_AMOUNT_ATOMIC`
|
||||
6. `DEPLOY_OUTPUT_PATH` (optional)
|
||||
|
||||
|
||||
47
contracts/MockERC20.sol
Normal file
47
contracts/MockERC20.sol
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract MockERC20 {
|
||||
string public name;
|
||||
string public symbol;
|
||||
uint8 public immutable decimals;
|
||||
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
|
||||
constructor(string memory name_, string memory symbol_, uint8 decimals_) {
|
||||
name = name_;
|
||||
symbol = symbol_;
|
||||
decimals = decimals_;
|
||||
}
|
||||
|
||||
function mint(address to, uint256 value) external {
|
||||
balanceOf[to] += value;
|
||||
emit Transfer(address(0), to, value);
|
||||
}
|
||||
|
||||
function approve(address spender, uint256 value) external returns (bool) {
|
||||
allowance[msg.sender][spender] = value;
|
||||
emit Approval(msg.sender, spender, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint256 value) external returns (bool) {
|
||||
uint256 allowed = allowance[from][msg.sender];
|
||||
if (allowed < value) {
|
||||
return false;
|
||||
}
|
||||
uint256 bal = balanceOf[from];
|
||||
if (bal < value) {
|
||||
return false;
|
||||
}
|
||||
allowance[from][msg.sender] = allowed - value;
|
||||
balanceOf[from] = bal - value;
|
||||
balanceOf[to] += value;
|
||||
emit Transfer(from, to, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,10 @@
|
||||
"chain_id": 84532,
|
||||
"deployer": "0x0000000000000000000000000000000000000000",
|
||||
"treasury_wallet": "0x0000000000000000000000000000000000000000",
|
||||
"mint_currency_address": "0x0000000000000000000000000000000000000000",
|
||||
"mint_amount_atomic": "5000000000000000",
|
||||
"mint_currency_address": "0x036CbD53842c5426634e7929541eC2318f3dCf7e",
|
||||
"mint_amount_atomic": "100000000",
|
||||
"membership_contract": "0x0000000000000000000000000000000000000000",
|
||||
"deployment_tx_hash": "0x",
|
||||
"deployed_at": "2026-02-18T00:00:00Z",
|
||||
"notes": "Fill from deployment output and keep under change control."
|
||||
}
|
||||
|
||||
|
||||
@ -78,4 +78,44 @@ describe("EdutHumanMembership", function () {
|
||||
|
||||
expect(await contract.membershipStatus(recipient.address)).to.equal(2);
|
||||
});
|
||||
|
||||
it("supports ERC20 settlement path for USDC-style pricing", async function () {
|
||||
const [deployer, treasury, sponsor, recipient] = await ethers.getSigners();
|
||||
const tokenFactory = await ethers.getContractFactory("MockERC20", deployer);
|
||||
const usdc = await tokenFactory.deploy("Mock USDC", "USDC", 6);
|
||||
await usdc.deployed();
|
||||
|
||||
const membershipFactory = await ethers.getContractFactory("EdutHumanMembership", deployer);
|
||||
const usdcPrice = ethers.BigNumber.from("100000000"); // 100 USDC with 6 decimals
|
||||
const contract = await membershipFactory.deploy(treasury.address, usdc.address, usdcPrice);
|
||||
await contract.deployed();
|
||||
|
||||
await (await usdc.mint(sponsor.address, usdcPrice)).wait();
|
||||
await (await usdc.connect(sponsor).approve(contract.address, usdcPrice)).wait();
|
||||
|
||||
const before = await usdc.balanceOf(treasury.address);
|
||||
await (await contract.connect(sponsor).mintMembership(recipient.address)).wait();
|
||||
const after = await usdc.balanceOf(treasury.address);
|
||||
|
||||
expect(after.sub(before).eq(usdcPrice)).to.equal(true);
|
||||
expect(await contract.hasMembership(recipient.address)).to.equal(true);
|
||||
});
|
||||
|
||||
it("fails ERC20 mint without allowance", async function () {
|
||||
const [deployer, treasury, sponsor, recipient] = await ethers.getSigners();
|
||||
const tokenFactory = await ethers.getContractFactory("MockERC20", deployer);
|
||||
const usdc = await tokenFactory.deploy("Mock USDC", "USDC", 6);
|
||||
await usdc.deployed();
|
||||
|
||||
const membershipFactory = await ethers.getContractFactory("EdutHumanMembership", deployer);
|
||||
const usdcPrice = ethers.BigNumber.from("100000000");
|
||||
const contract = await membershipFactory.deploy(treasury.address, usdc.address, usdcPrice);
|
||||
await contract.deployed();
|
||||
|
||||
await (await usdc.mint(sponsor.address, usdcPrice)).wait();
|
||||
await expectRevertWithCustomError(
|
||||
contract.connect(sponsor).mintMembership(recipient.address),
|
||||
"PaymentTransferFailed"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user