这份文档面向开发者阅读,按当前主实现梳理各核心合约的职责、关键外部接口、典型调用方和常见调用顺序。
建议先阅读 Architecture.md,再阅读本文。
当前主实现可分为四层:
- 主控层:Dao.sol,SourceDaoUpgradeable.sol
- 治理层:Committee.sol
- 生产层:Project.sol
- 资产层:DevToken.sol,NormalToken.sol,TokenLockup.sol,Dividend.sol,Acquired.sol
接口定义主要集中在 contracts/Interface.sol。
Dao.sol 是全系统的模块注册表。它本身不负责具体业务,而是作为其他模块查找彼此地址的统一入口。
-
initialize()
- 初始化主合约。
- 当前实现不带参数。
-
setDevTokenAddress(address)
-
setNormalTokenAddress(address)
-
setCommitteeAddress(address)
-
setProjectAddress(address)
-
setTokenLockupAddress(address)
-
setTokenDividendAddress(address)
-
setAcquiredAddress(address)
- 用于注册各模块地址。
- 所有地址都只能设置一次。
-
devToken()
-
normalToken()
-
committee()
-
project()
-
lockup()
-
dividend()
-
acquired()
- 返回各模块实例。
-
isDAOContract(address)
- 判断某个地址是否为 DAO 内部模块。
- 治理模块依赖这个接口限制只有内部模块才能发起某些提案。
- 部署脚本
- 其他主模块
- 链下读取脚本
- 这是一个只注册一次的总线,不是可随意替换地址的控制台。
- 模块初始化顺序会影响后续模块间调用是否可用。
这是所有主模块共享的升级基类,负责保存主合约地址,并把 UUPS 升级授权接到委员会治理上。
-
setMainContractAddress(address)
- 主合约地址只允许设置一次。
-
version()
- 返回当前实现版本。
-
升级授权逻辑
- 内部会调用委员会的 verifyContractUpgrade(newImplementation)。
- 也就是升级不由 owner 或单点管理员决定,而由治理结果决定。
- 各 UUPS 代理合约本身
- 委员会升级提案流程
Committee.sol 是治理核心,负责:
- 普通提案
- 全员提案
- 委员会成员变更
- DevToken 权重调整
- 合约升级提案
-
propose(duration, params)
- 发起普通提案。
- 只能由 DAO 内部模块调用。
- 典型调用者是 Project.sol。
-
fullPropose(duration, params, threshold)
- 发起全员提案。
- 也是内部模块调用。
- 适合需要全体 Token 持有人参与的治理动作。
-
support(proposalId, params)
-
reject(proposalId, params)
- 对提案投票。
- 会校验参数 Merkle 根是否一致。
-
settleProposal(proposalId)
- 主动结算普通提案。
-
takeResult(proposalId, params)
- 读取提案结果,同时在必要时触发结算。
- 业务合约在执行提案结果时通常走这个入口。
-
proposalOf(proposalId)
-
proposalExtraOf(proposalId)
- 查询提案基础信息和全员提案扩展信息。
-
setProposalExecuted(proposalId)
- 在业务动作已经执行完毕后,把提案标记为 Executed。
- endFullPropose(proposalId, voters)
- 用于分批结算全员提案。
- 由于全员投票可能涉及很多地址,当前设计允许分多次把 voter 列表提交进来累计结算。
- prepareAddMember(address)
- addCommitteeMember(address, proposalId)
- prepareRemoveMember(address)
- removeCommitteeMember(address, proposalId)
- prepareSetCommittees(address[], isFullProposal)
- setCommittees(address[], proposalId)
- 这些接口全部遵循先提案、后执行的两阶段模式。
- prepareSetDevRatio(newDevRatio)
- setDevRatio(newDevRatio, proposalId)
- 调整 DevToken 在全员投票中的权重。
- 正式版发布后不能再调整。
-
prepareContractUpgrade(proxy, newImplementation)
- 发起某个代理合约的升级提案。
-
verifyContractUpgrade(newImplementation)
- 被代理合约在执行升级时调用。
- 只有匹配的升级提案被通过后,才返回 true。
-
cancelContractUpgrade(proxy)
-
getContractUpgradeProposal(proxy)
- 用于取消或查询升级提案。
普通提案:
- 业务合约调用 propose。
- 成员调用 support 或 reject。
- 业务合约调用 takeResult。
- 如果结果为 Accept,业务合约执行动作。
- 业务合约调用 setProposalExecuted。
全员提案:
- 业务合约调用 fullPropose。
- 持有人调用 support 或 reject。
- 截止后分批调用 endFullPropose。
- 提案最终变为 Accept、Reject 或 Expired。
- 参数校验依赖 Merkle 根,执行动作时必须传入与提案一致的参数数组。
- setProposalExecuted 不只是状态美化,它是防止已通过提案被重复利用的重要步骤。
Project.sol 负责把一个开源项目从立项推进到结项,并把结项奖励按贡献比例发放给参与者。
- Preparing:等待立项提案
- Developing:开发中
- Accepting:等待验收提案
- Finished:已完成
- Rejected:失败或被拒绝
-
createProject(budget, name, version, startDate, endDate, extraTokens, extraTokenAmounts)
- 创建项目。
- 自动生成立项提案。
- 预算受 DevToken 总量上限约束。
-
cancelProject(projectId)
- 当对应提案失败或过期时取消项目。
- 如果项目附带了额外 Token,会退回给项目经理。
-
promoteProject(projectId)
- 在提案通过后推进项目状态。
- Preparing 进入 Developing。
- Accepting 进入 Finished,并执行奖励结算逻辑。
-
acceptProject(projectId, result, contributions)
- 由项目经理提交项目结果与贡献列表。
- 自动发起验收提案。
-
updateContribute(projectId, contribution)
- 在验收投票阶段修改或补充贡献值。
-
withdrawContributions(projectIds)
- 贡献者提取其在已完成项目中的奖励。
- 同时处理额外 Token 的分配。
-
projectOf(projectId)
-
projectDetailOf(projectId)
-
contributionOf(projectId, who)
-
latestProjectVersion(projectName)
-
versionReleasedTime(projectName, version)
- 提供项目状态、贡献、版本发布信息查询。
- 项目经理调用 createProject。
- 委员会对立项提案投票。
- 项目经理调用 promoteProject,把项目推进到 Developing。
- 项目经理调用 acceptProject,提交结果和贡献列表。
- 委员会对验收提案投票。
- 项目经理再次调用 promoteProject,触发奖励发放逻辑。
- 贡献者调用 withdrawContributions 领取奖励。
- 项目创建和项目验收都不是一步完成,而是两次提案驱动的状态机。
- latestProjectVersion 和 versionReleasedTime 不只是查询接口,还被锁仓和治理逻辑引用。
DevToken 是贡献权益 Token,不是自由流通 Token。
-
initialize(name, symbol, totalSupply, initAddress, initAmount, mainAddr)
- 初始化总量和预分配。
-
mintDevToken(amount)
- 只能由 Project.sol 调用。
- 本质上是从合约自身库存把奖励拨给项目模块。
-
dev2normal(amount)
- 把 DevToken 1:1 转换为 NormalToken。
-
totalReleased()
- 返回已释放总量。
- 转账逻辑被限制在少数合法路径上。
- 合法路径包括项目奖励、锁仓转换、分红质押等。
- 不要把 DevToken 按普通 ERC20 理解。
- 它更像一种受制度约束的贡献权益凭证。
NormalToken 是可流通 Token,用于转账、交易、投票计权和被收购。
- initialize(name, symbol, mainAddr)
- mintNormalToken(to, amount)
- 只能由 DevToken.sol 调用。
- 当前 NormalToken 的增发入口只来自 DevToken 转换。
- 这保证流通权益的来源仍然受贡献权益约束。
TokenLockup.sol 用于处理前期投资、资本合作或特殊分配所需的锁仓逻辑。
-
initialize(unlockProjectName, unlockVersion, mainAddr)
- 设置锁仓释放所绑定的项目名与版本号。
-
transferAndLock(to[], amount[])
- 直接转入 NormalToken 并锁定给一组地址。
-
convertAndLock(to[], amount[])
- 先把 DevToken 转为 NormalToken,再锁定给一组地址。
-
claimTokens(amount)
- 当目标版本正式发布后,按 6 个月线性释放规则提取已解锁额度。
-
getCanClaimTokens()
-
totalAssigned(owner)
-
totalClaimed(owner)
- 查询可领取额度、累计分配和累计领取。
- 一旦解锁开始,就不再适合继续接收新的锁仓批次。
- 解锁不是提案手动逐笔发放,而是绑定目标版本发布时间自动生效。
Dividend.sol 是收益池与质押池,允许外部收益注入,再按各周期质押份额分配给参与者。
-
initialize(cycleMinLength, mainAddr)
- 设置最短分红周期长度。
-
deposit(amount, token)
-
receive()
-
updateTokenBalance(token)
- 向分红池注入外部资产。
- 支持原生代币和非 DAO Token 的 ERC20。
-
stakeNormal(amount)
-
stakeDev(amount)
-
unstakeNormal(amount)
-
unstakeDev(amount)
- 质押和解除质押两类 Token。
-
tryNewCycle()
- 手动推动进入新周期。
-
getCurrentCycleIndex()
-
getCurrentCycle()
-
getCycleInfos(start, end)
-
getTotalStaked(cycleIndex)
-
getDepositTokenBalance(token)
-
getStakeAmount(cycleIndex)
- 查询周期和质押信息。
-
isDividendWithdrawed(cycleIndex, token)
-
estimateDividends(cycleIndexes, tokens)
-
withdrawDividends(cycleIndexes, tokens)
- 估算并提取某些周期中的分红。
- 当前设计明确禁止把 DAO 自己的两类 Token作为分红池充值资产。
- 质押份额是按周期快照思路计算,而不是实时按块结算。
Acquired.sol 提供一种外部资产收购 NormalToken 的机制,支持白名单和两阶段额度控制。
-
initialize(initInvestmentCount, mainAddr)
-
startInvestment(param)
- 发起一笔收购。
- 可使用原生币或 ERC20 作为支付资产。
- 支持白名单与第一阶段额度比例。
-
invest(investmentId, amount)
- 白名单地址按照规则用 NormalToken 购买外部资产。
-
endInvestment(investmentId)
- 发起人结束收购,拿回已收到的 NormalToken 和剩余未售资产。
-
getInvestmentInfo(investmentId)
-
isInWhiteList(investmentId, addr)
-
getAddressPercent(investmentId, addr)
-
getAddressInvestedAmount(investmentId, addr)
-
getAddressLeftAmount(investmentId, addr)
- 查询收购信息、白名单配额和个人可用额度。
- 这个模块的资产方向和项目奖励方向不同,它更接近二级分配和外部资产换入机制。
- 收购目标是 NormalToken,而不是 DevToken。
- Project.sol 调用 Committee.sol 发起提案。
- Committee.sol 给出结果。
- Project.sol 推进状态并调用 DevToken.sol 发放奖励。
- 用户持有 DevToken。
- 用户调用 dev2normal。
- DevToken.sol 销毁自身额度。
- NormalToken.sol 铸造对应数量的流通 Token。
- 某个版本正式发布。
- TokenLockup.sol 通过 Project.sol 的 versionReleasedTime 判断释放是否开始。
- 用户调用 claimTokens 按线性进度领取。
- 委员会成员发起升级提案。
- 委员会投票通过后,代理合约在升级时调用 verifyContractUpgrade。
- 验证通过才允许完成升级。
如果目标是读懂接口和调用关系,建议按下面顺序继续看代码:
- contracts/Interface.sol
- contracts/Dao.sol
- contracts/Committee.sol
- contracts/Project.sol
- contracts/DevToken.sol
- contracts/NormalToken.sol
- contracts/TokenLockup.sol
- contracts/Dividend.sol
- contracts/Acquired.sol
如果后续还要补文档,最自然的下一步是给每个合约补一张状态机图或权限矩阵。