505 lines
14 KiB
Plaintext
505 lines
14 KiB
Plaintext
///! 治理模块: DAO治理协议
|
||
///! NAC的去中心化治理标准协议
|
||
///!
|
||
///! **版本**: v1.0
|
||
///! **模块**: charter-std-zh/governance/governance.ch
|
||
|
||
使用 资产::gnacs::GNACS编码;
|
||
使用 主权::规则::主权类型;
|
||
|
||
// ============================================================================
|
||
// 治理接口定义
|
||
// ============================================================================
|
||
|
||
/// 治理接口
|
||
///
|
||
/// 定义DAO治理的标准操作
|
||
接口 治理 {
|
||
// ========== 查询函数 ==========
|
||
|
||
/// 查询治理代币地址
|
||
函数 治理代币() -> 地址;
|
||
|
||
/// 查询提案数量
|
||
函数 提案数量() -> u256;
|
||
|
||
/// 查询提案信息
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
函数 提案信息(提案ID: u256) -> 提案;
|
||
|
||
/// 查询提案状态
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
函数 提案状态(提案ID: u256) -> 提案状态枚举;
|
||
|
||
/// 查询用户投票权重
|
||
///
|
||
/// # 参数
|
||
/// - `用户`: 用户地址
|
||
/// - `区块号`: 快照区块号
|
||
函数 投票权重(用户: 地址, 区块号: u256) -> u256;
|
||
|
||
/// 查询用户是否已投票
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
/// - `用户`: 用户地址
|
||
函数 是否已投票(提案ID: u256, 用户: 地址) -> 布尔;
|
||
|
||
/// 查询提案投票结果
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
函数 投票结果(提案ID: u256) -> (u256, u256, u256); // (赞成, 反对, 弃权)
|
||
|
||
/// 查询提案阈值
|
||
函数 提案阈值() -> u256;
|
||
|
||
/// 查询投票阈值
|
||
函数 投票阈值() -> u256;
|
||
|
||
/// 查询投票延迟
|
||
函数 投票延迟() -> u256;
|
||
|
||
/// 查询投票期限
|
||
函数 投票期限() -> u256;
|
||
|
||
// ========== 状态变更函数 ==========
|
||
|
||
/// 创建提案
|
||
///
|
||
/// # 参数
|
||
/// - `标题`: 提案标题
|
||
/// - `描述`: 提案描述
|
||
/// - `目标`: 执行目标合约地址数组
|
||
/// - `数值`: 执行调用的ETH数值数组
|
||
/// - `调用数据`: 执行调用的数据数组
|
||
///
|
||
/// # 返回
|
||
/// - `u256`: 提案ID
|
||
///
|
||
/// # 事件
|
||
/// - `提案创建事件`
|
||
函数 创建提案(
|
||
标题: 字符串,
|
||
描述: 字符串,
|
||
目标: 数组<地址>,
|
||
数值: 数组<u256>,
|
||
调用数据: 数组<字节数组>
|
||
) -> u256;
|
||
|
||
/// 投票
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
/// - `支持`: 投票选项(0=反对, 1=赞成, 2=弃权)
|
||
///
|
||
/// # 事件
|
||
/// - `投票事件`
|
||
函数 投票(提案ID: u256, 支持: u8);
|
||
|
||
/// 执行提案
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
///
|
||
/// # 事件
|
||
/// - `提案执行事件`
|
||
函数 执行提案(提案ID: u256);
|
||
|
||
/// 取消提案
|
||
///
|
||
/// # 参数
|
||
/// - `提案ID`: 提案唯一标识符
|
||
///
|
||
/// # 事件
|
||
/// - `提案取消事件`
|
||
函数 取消提案(提案ID: u256);
|
||
}
|
||
|
||
// ============================================================================
|
||
// 数据结构定义
|
||
// ============================================================================
|
||
|
||
/// 提案结构
|
||
结构 提案 {
|
||
/// 提案ID
|
||
ID: u256,
|
||
/// 提案者
|
||
提案者: 地址,
|
||
/// 提案标题
|
||
标题: 字符串,
|
||
/// 提案描述
|
||
描述: 字符串,
|
||
/// 执行目标
|
||
目标: 数组<地址>,
|
||
/// 执行数值
|
||
数值: 数组<u256>,
|
||
/// 执行调用数据
|
||
调用数据: 数组<字节数组>,
|
||
/// 开始区块
|
||
开始区块: u256,
|
||
/// 结束区块
|
||
结束区块: u256,
|
||
/// 赞成票数
|
||
赞成票数: u256,
|
||
/// 反对票数
|
||
反对票数: u256,
|
||
/// 弃权票数
|
||
弃权票数: u256,
|
||
/// 是否已执行
|
||
已执行: 布尔,
|
||
/// 是否已取消
|
||
已取消: 布尔
|
||
}
|
||
|
||
/// 提案状态枚举
|
||
枚举 提案状态枚举 {
|
||
/// 待投票
|
||
待投票,
|
||
/// 投票中
|
||
投票中,
|
||
/// 已通过
|
||
已通过,
|
||
/// 已拒绝
|
||
已拒绝,
|
||
/// 已执行
|
||
已执行,
|
||
/// 已取消
|
||
已取消
|
||
}
|
||
|
||
// ============================================================================
|
||
// 治理事件定义
|
||
// ============================================================================
|
||
|
||
/// 提案创建事件
|
||
事件 提案创建事件 {
|
||
提案ID: u256,
|
||
提案者: 地址,
|
||
标题: 字符串,
|
||
描述: 字符串,
|
||
开始区块: u256,
|
||
结束区块: u256
|
||
}
|
||
|
||
/// 投票事件
|
||
事件 投票事件 {
|
||
投票者: 地址,
|
||
提案ID: u256,
|
||
支持: u8,
|
||
权重: u256
|
||
}
|
||
|
||
/// 提案执行事件
|
||
事件 提案执行事件 {
|
||
提案ID: u256
|
||
}
|
||
|
||
/// 提案取消事件
|
||
事件 提案取消事件 {
|
||
提案ID: u256
|
||
}
|
||
|
||
// ============================================================================
|
||
// 治理基础实现
|
||
// ============================================================================
|
||
|
||
/// 治理基础实现
|
||
合约 治理基础 实现 治理 {
|
||
// ========== 状态变量 ==========
|
||
|
||
私有 _治理代币: 地址;
|
||
私有 _提案数量: u256;
|
||
私有 _提案映射: 映射<u256, 提案>;
|
||
私有 _已投票映射: 映射<u256, 映射<地址, 布尔>>;
|
||
私有 _提案阈值: u256; // 创建提案所需的最小代币数量
|
||
私有 _投票阈值: u256; // 提案通过所需的最小赞成票比例(基点,10000=100%)
|
||
私有 _投票延迟: u256; // 提案创建后到投票开始的区块数
|
||
私有 _投票期限: u256; // 投票持续的区块数
|
||
|
||
// ========== 构造函数 ==========
|
||
|
||
构造函数(
|
||
治理代币: 地址,
|
||
提案阈值: u256,
|
||
投票阈值: u256,
|
||
投票延迟: u256,
|
||
投票期限: u256
|
||
) {
|
||
要求(治理代币 != 地址::零地址(), "治理: 代币地址无效");
|
||
要求(投票阈值 <= 10000, "治理: 投票阈值无效");
|
||
|
||
_治理代币 = 治理代币;
|
||
_提案阈值 = 提案阈值;
|
||
_投票阈值 = 投票阈值;
|
||
_投票延迟 = 投票延迟;
|
||
_投票期限 = 投票期限;
|
||
_提案数量 = 0;
|
||
}
|
||
|
||
// ========== 查询函数实现 ==========
|
||
|
||
函数 治理代币() -> 地址 {
|
||
返回 _治理代币;
|
||
}
|
||
|
||
函数 提案数量() -> u256 {
|
||
返回 _提案数量;
|
||
}
|
||
|
||
函数 提案信息(提案ID: u256) -> 提案 {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
返回 _提案映射.获取(提案ID);
|
||
}
|
||
|
||
函数 提案状态(提案ID: u256) -> 提案状态枚举 {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
|
||
让 提案 = 提案信息(提案ID);
|
||
|
||
如果 提案.已取消 {
|
||
返回 提案状态枚举::已取消;
|
||
}
|
||
|
||
如果 提案.已执行 {
|
||
返回 提案状态枚举::已执行;
|
||
}
|
||
|
||
让 当前区块 = 区块::号();
|
||
|
||
如果 当前区块 < 提案.开始区块 {
|
||
返回 提案状态枚举::待投票;
|
||
}
|
||
|
||
如果 当前区块 <= 提案.结束区块 {
|
||
返回 提案状态枚举::投票中;
|
||
}
|
||
|
||
// 投票已结束,检查是否通过
|
||
让 总票数 = 提案.赞成票数 + 提案.反对票数 + 提案.弃权票数;
|
||
如果 总票数 == 0 {
|
||
返回 提案状态枚举::已拒绝;
|
||
}
|
||
|
||
让 赞成比例 = 提案.赞成票数 * 10000 / 总票数;
|
||
如果 赞成比例 >= _投票阈值 {
|
||
返回 提案状态枚举::已通过;
|
||
} 否则 {
|
||
返回 提案状态枚举::已拒绝;
|
||
}
|
||
}
|
||
|
||
函数 投票权重(用户: 地址, 区块号: u256) -> u256 {
|
||
// 简化实现:使用当前余额
|
||
// 实际应该使用快照机制
|
||
返回 ACC20(_治理代币).持有量(用户);
|
||
}
|
||
|
||
函数 是否已投票(提案ID: u256, 用户: 地址) -> 布尔 {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
返回 _已投票映射.获取或默认(提案ID, 映射::新建()).获取或默认(用户, 假);
|
||
}
|
||
|
||
函数 投票结果(提案ID: u256) -> (u256, u256, u256) {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
让 提案 = 提案信息(提案ID);
|
||
返回 (提案.赞成票数, 提案.反对票数, 提案.弃权票数);
|
||
}
|
||
|
||
函数 提案阈值() -> u256 {
|
||
返回 _提案阈值;
|
||
}
|
||
|
||
函数 投票阈值() -> u256 {
|
||
返回 _投票阈值;
|
||
}
|
||
|
||
函数 投票延迟() -> u256 {
|
||
返回 _投票延迟;
|
||
}
|
||
|
||
函数 投票期限() -> u256 {
|
||
返回 _投票期限;
|
||
}
|
||
|
||
// ========== 状态变更函数实现 ==========
|
||
|
||
函数 创建提案(
|
||
标题: 字符串,
|
||
描述: 字符串,
|
||
目标: 数组<地址>,
|
||
数值: 数组<u256>,
|
||
调用数据: 数组<字节数组>
|
||
) -> u256 {
|
||
// 检查提案者权重
|
||
让 提案者权重 = 投票权重(消息::发送者(), 区块::号());
|
||
要求(提案者权重 >= _提案阈值, "治理: 代币数量不足");
|
||
|
||
// 检查参数
|
||
要求(目标.长度() == 数值.长度() && 目标.长度() == 调用数据.长度(),
|
||
"治理: 参数长度不匹配");
|
||
要求(目标.长度() > 0, "治理: 目标为空");
|
||
|
||
// 创建提案
|
||
让 提案ID = _提案数量;
|
||
让 开始区块 = 区块::号() + _投票延迟;
|
||
让 结束区块 = 开始区块 + _投票期限;
|
||
|
||
让 新提案 = 提案 {
|
||
ID: 提案ID,
|
||
提案者: 消息::发送者(),
|
||
标题: 标题,
|
||
描述: 描述,
|
||
目标: 目标,
|
||
数值: 数值,
|
||
调用数据: 调用数据,
|
||
开始区块: 开始区块,
|
||
结束区块: 结束区块,
|
||
赞成票数: 0,
|
||
反对票数: 0,
|
||
弃权票数: 0,
|
||
已执行: 假,
|
||
已取消: 假
|
||
};
|
||
|
||
_提案映射.插入(提案ID, 新提案);
|
||
_提案数量 = _提案数量 + 1;
|
||
|
||
触发 提案创建事件 {
|
||
提案ID: 提案ID,
|
||
提案者: 消息::发送者(),
|
||
标题: 标题,
|
||
描述: 描述,
|
||
开始区块: 开始区块,
|
||
结束区块: 结束区块
|
||
};
|
||
|
||
返回 提案ID;
|
||
}
|
||
|
||
函数 投票(提案ID: u256, 支持: u8) {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
要求(支持 <= 2, "治理: 投票选项无效");
|
||
要求(!是否已投票(提案ID, 消息::发送者()), "治理: 已投票");
|
||
|
||
让 状态 = 提案状态(提案ID);
|
||
要求(状态 == 提案状态枚举::投票中, "治理: 不在投票期");
|
||
|
||
// 获取投票权重
|
||
让 提案 = 提案信息(提案ID);
|
||
让 权重 = 投票权重(消息::发送者(), 提案.开始区块);
|
||
要求(权重 > 0, "治理: 无投票权");
|
||
|
||
// 记录投票
|
||
让 可变 已投票映射 = _已投票映射.获取或默认(提案ID, 映射::新建());
|
||
已投票映射.插入(消息::发送者(), 真);
|
||
_已投票映射.插入(提案ID, 已投票映射);
|
||
|
||
// 更新票数
|
||
让 可变 提案 = _提案映射.获取(提案ID);
|
||
如果 支持 == 0 {
|
||
提案.反对票数 = 提案.反对票数 + 权重;
|
||
} 否则如果 支持 == 1 {
|
||
提案.赞成票数 = 提案.赞成票数 + 权重;
|
||
} 否则 {
|
||
提案.弃权票数 = 提案.弃权票数 + 权重;
|
||
}
|
||
_提案映射.插入(提案ID, 提案);
|
||
|
||
触发 投票事件 {
|
||
投票者: 消息::发送者(),
|
||
提案ID: 提案ID,
|
||
支持: 支持,
|
||
权重: 权重
|
||
};
|
||
}
|
||
|
||
函数 执行提案(提案ID: u256) {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
|
||
让 状态 = 提案状态(提案ID);
|
||
要求(状态 == 提案状态枚举::已通过, "治理: 提案未通过");
|
||
|
||
让 可变 提案 = _提案映射.获取(提案ID);
|
||
要求(!提案.已执行, "治理: 已执行");
|
||
|
||
// 执行提案
|
||
对于 i 在 0..提案.目标.长度() {
|
||
让 目标 = 提案.目标[i];
|
||
让 数值 = 提案.数值[i];
|
||
让 数据 = 提案.调用数据[i];
|
||
|
||
// 调用目标合约
|
||
让 (成功, _) = 目标.调用带值(数值, 数据);
|
||
要求(成功, "治理: 执行失败");
|
||
}
|
||
|
||
提案.已执行 = 真;
|
||
_提案映射.插入(提案ID, 提案);
|
||
|
||
触发 提案执行事件 {
|
||
提案ID: 提案ID
|
||
};
|
||
}
|
||
|
||
函数 取消提案(提案ID: u256) {
|
||
要求(提案ID < _提案数量, "治理: 提案不存在");
|
||
|
||
让 可变 提案 = _提案映射.获取(提案ID);
|
||
要求(消息::发送者() == 提案.提案者, "治理: 非提案者");
|
||
要求(!提案.已执行, "治理: 已执行");
|
||
要求(!提案.已取消, "治理: 已取消");
|
||
|
||
让 状态 = 提案状态(提案ID);
|
||
要求(状态 == 提案状态枚举::待投票 || 状态 == 提案状态枚举::投票中,
|
||
"治理: 无法取消");
|
||
|
||
提案.已取消 = 真;
|
||
_提案映射.插入(提案ID, 提案);
|
||
|
||
触发 提案取消事件 {
|
||
提案ID: 提案ID
|
||
};
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 时间锁接口
|
||
// ============================================================================
|
||
|
||
/// 时间锁接口
|
||
///
|
||
/// 定义延迟执行的标准操作
|
||
接口 时间锁 {
|
||
/// 查询最小延迟
|
||
函数 最小延迟() -> u256;
|
||
|
||
/// 查询最大延迟
|
||
函数 最大延迟() -> u256;
|
||
|
||
/// 查询操作是否已排队
|
||
函数 是否已排队(操作ID: 哈希) -> 布尔;
|
||
|
||
/// 排队操作
|
||
函数 排队(
|
||
目标: 地址,
|
||
数值: u256,
|
||
数据: 字节数组,
|
||
执行时间: u256
|
||
) -> 哈希;
|
||
|
||
/// 执行操作
|
||
函数 执行(
|
||
目标: 地址,
|
||
数值: u256,
|
||
数据: 字节数组
|
||
);
|
||
|
||
/// 取消操作
|
||
函数 取消(操作ID: 哈希);
|
||
}
|