前言本文作为上一篇STEPN相关内容的延续,将依托OpenZeppelinV5框架与Solidity0.8.24版本,重点拆解其核心创新点,具体涵盖Haus系统、能量系统、代币经济体系以及更简洁易用的交互体验四大模块,深入解析各创新点的设计逻辑与实现思路。STEPNGO概述STEPNG
本文作为上一篇STEPN相关内容的延续,将依托OpenZeppelinV5框架与Solidity0.8.24版本,重点拆解其核心创新点,具体涵盖Haus系统、能量系统、代币经济体系以及更简洁易用的交互体验四大模块,深入解析各创新点的设计逻辑与实现思路。
STEPN GO概述
STEPN GO 是由 FSL(Find Satoshi Lab)开发的全新 Web3 社交生活应用,被视为 STEPN 的“2.0 升级版”。它在延续“运动赚币(M2E)”核心逻辑的基础上,针对经济循环和社交门槛做了重大革新。
核心机制与创新点
- Haus 系统 (社交与租借):
从开发者和经济模型的角度来看,Stepn Go 是对原版 Stepn 痛点的全面升级,核心逻辑从“单币产出”转向了“资源平衡”和“社交门槛”。
| 对比维度 | Stepn | Stepn Go |
|---|---|---|
| 准入门槛与社交机制 | 独狼模式,购买 Sneaker NFT 即可参与,后期废除激活码,玩家间无强绑定 | 门票 / 抽奖模式,新手需老用户邀请或代币锁定抽奖获取鞋子,The Haus 组队 / 抽奖系统限制 Bot 增长,利益向老用户倾斜 |
| 经济循环(代币与消耗) | 双币制(GST/GMT),GST 近乎无限产出,仅消耗代币,用户增长放缓后易通胀崩盘 | 双币制,新增「Burning for Energy」,强制焚烧 Sneaker NFT 换取能量,以 NFT 消耗构建强底层通缩模型 |
| 数学模型差异(HP 与维修) | 后期新增 HP 衰减,维修主要消耗 GST,机制简单 | HP 损耗与效率挂钩,强制执行自动维修 / 高额 HP 维护成本,GGT 大量回流 / 销毁 |
| 角色属性与收益计算 | 属性简单(Efficiency、Luck、Comfort、Resilience) | 属性更丰富,新增套装属性、社交等级收益加成 |
burnSneakerForEnergy 函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract GGTToken is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); constructor() ERC20("Go Game Token", "GGT") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); } function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } }
contract StepnGoIntegrated is ERC721, AccessControl, ReentrancyGuard { GGTToken public immutable ggt; bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
struct Sneaker { uint256 level; uint256 efficiency; uint256 hp; }
struct HausLease { address guest; uint256 guestShare; }
mapping(uint256 => Sneaker) public sneakers;
mapping(uint256 => HausLease) public hausRegistry;
mapping(address => uint256) public permanentEnergy;
uint256 private _nextTokenId;
constructor(address _ggt) ERC721("StepnGo Sneaker", "SNK") {
ggt = GGTToken(_ggt);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mintSneaker(address to, uint256 eff) external onlyRole(DEFAULT_ADMIN_ROLE) returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
sneakers[tokenId] = Sneaker(1, eff, 10000);
return tokenId;
}
function setHausLease(uint256 tokenId, address guest, uint256 share) external {
require(ownerOf(tokenId) == msg.sender, "Not owner");
hausRegistry[tokenId] = HausLease(guest, share);
}
function burnForEnergy(uint256 tokenId) external {
require(ownerOf(tokenId) == msg.sender, "Not owner");
_burn(tokenId);
permanentEnergy[msg.sender] += 1;
}
function settleWorkout(uint256 tokenId, uint256 km) external onlyRole(ORACLE_ROLE) nonReentrant {
Sneaker storage snk = sneakers[tokenId];
require(snk.hp > 1000, "Low HP");
uint256 totalReward = km * snk.efficiency * 10**16;
snk.hp -= (km * 100);
address host = ownerOf(tokenId);
HausLease memory lease = hausRegistry[tokenId];
if (lease.guest != address(0)) {
uint256 guestAmt = (totalReward * lease.guestShare) / 100;
ggt.mint(lease.guest, guestAmt);
ggt.mint(host, totalReward - guestAmt);
} else { ggt.mint(host, totalReward); }
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
* **GGTToken合约**
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract GGTToken is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() ERC20("Go Game Token", "GGT") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) {
_burn(from, amount);
}
}
contract StepnGoEngine is ReentrancyGuard, AccessControl { GGTToken public immutable ggt;
struct SneakerStats {
uint256 level;
uint256 efficiency; // 影响产出
uint256 hp; // 10000 基数 (100.00%)
}
mapping(uint256 => SneakerStats) public sneakers;
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
event WorkoutProcessed(uint256 indexed tokenId, uint256 netGGT, uint256 hpLoss);
constructor(address _ggt) {
ggt = GGTToken(_ggt);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// 核心数学模型:结算运动奖励并扣除维修费(HP 损耗)
function settleGGT(uint256 tokenId, uint256 km) external onlyRole(ORACLE_ROLE) nonReentrant {
SneakerStats storage snk = sneakers[tokenId];
require(snk.hp > 1000, "HP too low, need repair"); // 低于 10% 无法运动
// 1. 产出公式: Reward = km * Efficiency * log(Level) 简化版
uint256 rawReward = km * snk.efficiency * 10**15;
// 2. HP 损耗公式: Loss = km * (Level^0.5)
uint256 hpLoss = km * 100; // 模拟每公里掉 1%
if (snk.hp > hpLoss) {
snk.hp -= hpLoss;
} else {
snk.hp = 0;
}
// 3. 自动维修逻辑 (经济循环核心):
// 假设系统强制扣除 10% 的产出用于“销毁”以维持生态,模拟强制维修费
uint256 maintenanceFee = rawReward / 10;
uint256 netReward = rawReward - maintenanceFee;
ggt.mint(tx.origin, netReward); // 发放净收益
// 模拟销毁:如果已经产生了 GGT,此处可以 burn 掉维修费部分
emit WorkoutProcessed(tokenId, netReward, hpLoss);
}
function initializeSneaker(uint256 tokenId, uint256 level, uint256 eff) external onlyRole(DEFAULT_ADMIN_ROLE) {
sneakers[tokenId] = SneakerStats(level, eff, 10000);
}
}
### 测试脚本
* **StepnGo测试**
* **Haus 租赁分润 + HP 损耗结算**
* **销毁运动鞋增加永久能量**
import assert from "node:assert/strict"; import { describe, it, beforeEach } from "node:test"; import { network } from "hardhat"; // 或者直接从 global 获取 import { parseEther, keccak256, stringToBytes } from "viem";
describe("STEPN GO 核心业务闭环测试", function () { let core: any, ggt: any; let admin: any, host: any, guest: any; let publicClient: any;
beforeEach(async function () {
const { viem: v } = await (network as any).connect();
[admin, host, guest] = await v.getWalletClients();
publicClient = await v.getPublicClient();
// 1. 部署 GGT 和 Core
ggt = await v.deployContract("contracts/StepnGoIntegrated.sol:GGTToken");
core = await v.deployContract("contracts/StepnGoIntegrated.sol:StepnGoIntegrated", [ggt.address]);
// 2. 角色授权
const MINTER_ROLE = keccak256(stringToBytes("MINTER_ROLE"));
const ORACLE_ROLE = keccak256(stringToBytes("ORACLE_ROLE"));
await ggt.write.grantRole([MINTER_ROLE, core.address]);
await core.write.grantRole([ORACLE_ROLE, admin.account.address]);
});
it("创新点测试:Haus 租赁分润 + HP 损耗结算", async function () {
// A. 铸造并设置 30% 分成给 Guest
await core.write.mintSneaker([host.account.address, 50n]);
await core.write.setHausLease([0n, guest.account.address, 30n], { account: host.account });
// B. 结算 10km (奖励 5e18)
await core.write.settleWorkout([0n, 10n]);
// C. 验证 Guest 收到 1.5e18 (30%)
const guestBalance = await ggt.read.balanceOf([guest.account.address]);
assert.strictEqual(guestBalance, 1500000000000000000n, "Guest 分润金额不正确");
// D. 验证 HP 损耗 (10000 - 10*100 = 9000)
const snk = await core.read.sneakers([0n]);
assert.strictEqual(snk[2], 9000n, "HP 损耗计算不正确");
});
it("创新点测试:销毁运动鞋增加永久能量", async function () {
// A. 给 Host 铸造一双鞋
await core.write.mintSneaker([host.account.address, 20n]);
// B. Host 销毁该鞋
await core.write.burnForEnergy([0n], { account: host.account });
// C. 验证能量增加且 NFT 消失
const energy = await core.read.permanentEnergy([host.account.address]);
assert.strictEqual(energy, 1n, "能量点数未增加");
try {
await core.read.ownerOf([0n]);
assert.fail("NFT 未被正确销毁");
} catch (e: any) {
assert.ok(e.message.includes("ERC721NonexistentToken"), "报错信息不符合预期");
}
});
});
* **GGTToken测试**
* **正确计算收益并扣除 HP**
* **HP 低于 10% 时应拒绝运动**
import assert from "node:assert/strict"; import { describe, it, beforeEach } from "node:test"; import { network } from "hardhat"; import { parseUnits, decodeEventLog, keccak256, toBytes, getAddress } from 'viem';
describe("StepnGo Engine Logic (Viem + Node Test)", function () { let ggt: any; let engine: any; let publicClient: any; let admin: any, oracle: any, user: any;
const TOKEN_ID = 101n;
// 权限哈希定义
const MINTER_ROLE = keccak256(toBytes("MINTER_ROLE"));
const ORACLE_ROLE = keccak256(toBytes("ORACLE_ROLE"));
beforeEach(async function () {
const { viem } = await (network as any).connect();
publicClient = await viem.getPublicClient();
[admin, oracle, user] = await viem.getWalletClients();
// --- 修复点 1: 使用完全限定名解决 HHE1001 ---
ggt = await viem.deployContract("contracts/GGT.sol:GGTToken", []);
engine = await viem.deployContract("contracts/GGT.sol:StepnGoEngine", [ggt.address]);
// 权限授权
await ggt.write.grantRole([MINTER_ROLE, engine.address], { account: admin.account });
await engine.write.grantRole([ORACLE_ROLE, oracle.account.address], { account: admin.account });
// 初始化
await engine.write.initializeSneaker([TOKEN_ID, 5n, 10n], { account: admin.account });
});
describe("Settlement & Economy", function () {
it("应该正确计算收益并扣除 HP", async function () {
const km = 10n;
const txHash = await engine.write.settleGGT([TOKEN_ID, km], { account: oracle.account });
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
// 1. 验证 HP
const [,, currentHP] = await engine.read.sneakers([TOKEN_ID]);
assert.equal(currentHP, 9000n);
// --- 修复点 2: 健壮解析事件 ---
// 过滤出属于 WorkoutProcessed 的日志 (对比 topic0)
const workoutEventTopic = keccak256(toBytes("WorkoutProcessed(uint256,uint256,uint256)"));
const log = receipt.logs.find((l: any) => l.topics[0] === workoutEventTopic);
if (!log) throw new Error("WorkoutProcessed event not found");
const event = decodeEventLog({
abi: engine.abi,
eventName: 'WorkoutProcessed',
data: log.data,
topics: log.topics,
});
const expectedNet = parseUnits("90", 15);
assert.equal((event.args as any).netGGT, expectedNet);
// 验证 Oracle 余额 (tx.origin)
const balance = await ggt.read.balanceOf([oracle.account.address]);
assert.equal(balance, expectedNet);
});
it("当 HP 低于 10% 时应拒绝运动", async function () {
// 消耗 HP 至 900
await engine.write.settleGGT([TOKEN_ID, 91n], { account: oracle.account });
// --- 修复点 3: 捕获异步报错 ---
await assert.rejects(
async () => {
await engine.write.settleGGT([TOKEN_ID, 1n], { account: oracle.account });
},
(err: any) => {
const msg = err.message || "";
return msg.includes("HP too low") || msg.includes("Transaction reverted");
}
);
});
});
});
### 部署脚本
// scripts/deploy.js import { network, artifacts } from "hardhat"; import { parseUnits } from "viem"; async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端 const [deployer, investor] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress);
const GGTTokenArtifact = await artifacts.readArtifact("contracts/StepnGoIntegrated.sol:GGTToken");
const StepnGoIntegratedArtifact = await artifacts.readArtifact("contracts/StepnGoIntegrated.sol:StepnGoIntegrated");
// 1. 部署合约并获取交易哈希
const GGTTokenHash = await deployer.deployContract({
abi: GGTTokenArtifact.abi,
bytecode: GGTTokenArtifact.bytecode,
args: [],
});
const GGTTokenReceipt = await publicClient.waitForTransactionReceipt({
hash: GGTTokenHash
});
console.log("GGTToken合约地址:", GGTTokenReceipt.contractAddress);
// 2. 部署StepnGoIntegrated合约并获取交易哈希
const StepnGoIntegratedHash = await deployer.deployContract({
abi: StepnGoIntegratedArtifact.abi,
bytecode: StepnGoIntegratedArtifact.bytecode,
args: [GGTTokenReceipt.contractAddress],
});
const StepnGoIntegratedReceipt = await publicClient.waitForTransactionReceipt({
hash: StepnGoIntegratedHash
});
console.log("StepnGoIntegrated合约地址:", StepnGoIntegratedReceipt.contractAddress);
}
main().catch(console.error);
# 结语
本次围绕 STEPN 与 STEPN GO 核心差异的拆解,已完成从理论分析到基于 OpenZeppelin V5+Solidity 0.8.24 的代码落地。这一技术栈的选型,既依托 OpenZeppelin V5 的安全组件筑牢合约基础,也借助 Solidity 0.8.24 的特性适配不同场景需求 ——STEPN 合约聚焦「运动 - 激励」完整经济闭环,而 STEPN GO 则做了轻量化重构,剥离冗余逻辑以适配高频、轻量化的使用场景。
此次实践不仅厘清了两款产品的底层技术分野,也验证了成熟开源工具链在区块链应用开发中的核心价值:以产品定位为导向,通过精准的合约逻辑设计,让技术落地真正匹配产品的差异化诉求。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!