本文介绍了一种通过分叉模拟执行的方式来预先审查治理的执行动作。
- 原文链接: https://hackernoon.com/how-to-review-a-governance-action
- 译文出自:登链翻译计划
- 译者:Tiny 熊 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
一个良好的治理需要对执行后果有一个清晰的认识。
当我为金融机构工作时,严格的变革管理流程保护了技术在变革期间不受意外伤害。高层职员负责实现变化,但他们繁忙的日程安排和变化的复杂性往往使之具有挑战性。
区块链技术正在重塑金融,包括变革管理流程。我们已经成功地通过使用multisigs(多签)和DAO来让高层职员对变更负责,但在确保这些签署者了解他们所签署的内容方面,我们还没有那么成功。
恳请他们签署依然是有必要。尽管存在治理攻击的真实风险,但在不了解其影响的情况下简单地签署,已经有点成为一个笑话。
在Yield工作的早期,我关注一个问题:很多成员不理解他们所签署的内容。这对我来说是不可接受的。
如果你的多签成员不确定他们签署的是什么,有一些方法可以使他们的过程更容易。我将告诉你怎么做。
一些区块链应用程序,如Uniswap,是无权限的,部署后不需要进一步的行动。然而,大多数其他应用程序都采用了按治理模式进行更改的方式,只要有多人签名或DAO签名,就可以进行更改。
有些变化很简单,如转账代币或调整一个参数,而有些变化则更复杂,涉及部署合约和协调它们。
为任何变化获取签名都需要大量的努力,所以通常会要求为变化中包含的所有元素提供一个签名。在Yield,我见过的最大的变化包括三百多个单独的调用。
对于比较简单的变化,多签成员可能愿意审查调用数据,甚至可能有能力理解它。然而,对于更复杂的变化,这是不可能的。
授权执行变更的治理者应该知道哪些函数正在被调用,使用了哪些参数,以及这些将产生什么影响。他们应该能够自己找到这些信息,而不需要别人的帮助。
要做到这一点是可能的,而且比看起来要容易。然而,首先,我们必须了解治理变化是如何由智能合约执行的。
在Yield,我们建立了自己的Timelock,从Compound获得灵感,但做了一个关键的改变。原来的Timelock可以存储和延迟单个调用,而我们的Timelock可以存储一批(任何数量)调用。
其他协议可能使用不同的合约,但基本过程是相同的:一批函数调用被记录、授权,然后执行。
在我们的Timelock中,一个调用是一个目标和代表目标上的调用的调用数据。
struct Proposal {
STATE state;
uint32 eta;
}
一个提案或变化,只是一个调用的数组:
function propose(Call[] calldata functionCalls)
external override auth returns (bytes32 txHash)
{
txHash = keccak256(abi.encode(functionCalls));
require(proposals[txHash].state == STATE.UNKNOWN, "Already proposed.");
proposals[txHash].state = STATE.PROPOSED;
emit Proposed(txHash);
}
propose函数是有权限的,在我们的实现中,只允许我们的开发人员使用。
还有一个授权函数,只能由multisig 多签调用。授权时间决定了最早的可能执行时间:
function approve(bytes32 txHash)
external override auth returns (uint32 eta)
{
Proposal memory proposal = proposals[txHash];
require(proposal.state == STATE.PROPOSED, "Not proposed.");
eta = uint32(block.timestamp) + delay;
proposal.state = STATE.APPROVED;
proposal.eta = eta;
proposals[txHash] = proposal;
emit Approved(txHash, eta);
}
只有开发人员被允许执行一个变化,他们必须提供与propose函数中使用的相同的调用参数。如果参数不同或者变更标识符不正确,变更将无法找到授权,并且不会被执行。
function execute(Call[] calldata functionCalls)
external override auth returns (bytes[] memory results)
{
bytes32 txHash = keccak256(abi.encode(functionCalls));
Proposal memory proposal = proposals[txHash];
require(proposal.state == STATE.APPROVED, "Not approved.");
require(uint32(block.timestamp) >= proposal.eta, "ETA not reached.");
require(uint32(block.timestamp) <= proposal.eta + GRACE_PERIOD, "Proposal is stale.");
delete proposals[txHash];
results = new bytes[](functionCalls.length);
for (uint256 i = 0; i < functionCalls.length; i++){
require(functionCalls[i].target.isContract(), "Call to a non-contract");
(bool success, bytes memory result) = functionCalls[i].target.call(functionCalls[i].data);
if (!success) revert(RevertMsgExtractor.getRevertMsg(result));
results[i] = result;
}
emit Executed(txHash);
}
考虑到,可能会有任何人来实现变更,把这个想法变成代码。实现的代码,以一批函数调用的形式,用propose
送入Timelock。
下一步是由负责的高层职员、多签或DAO来审查这个变化,并决定是否授权它,调用approve
。
这一步往往是困难的一步,因为从Timelock中理解提议的变化是困难的。我们可以通过使用现有的开发工具,为他们提供关于变更性质的详细信息,使这个过程变得更容易。
一旦一个变化在Timelock中被提出,由于其独特的标识符,它就成为不可改变的。如果一个治理者授权了这个标识符,那么只有被授权的那批确切的调用可以被执行。
考虑到这一点,我们可以:
approve
)该提案。正在执行的变化与提议的变化是一样的,因为我们在提议后分叉了网络。不仅如此,任何人都可以用交易解码器分析执行情况。
在Yield,我们使用Tenderly,但也有其他可用的选择。出于教学目的,我公开了我们在分叉中的一个提案执行
作为Yield的一个多签成员,我审查了分叉中每个提案的执行情况。我将多签中需要授权的变更标识符与分叉中的变更标识符进行比较,然后我就完全肯定地知道多签中的内容就是会被执行的内容:
验证执行标识符
所有的改变都必须包括基于文本的描述,说明其目的和使用的参数。我利用这一点来了解该变化在广义上的作用,然后使用交易解码器来审查细节。
我首先将描述与执行中涉及的合约进行比较,以确保它们相匹配。在这个过程中进行尽职调查,包括验证所有新的合约,并确保他们验证的代码是代码库中的内容:
审查涉及的合约
知道了所涉及的合约,交易的事件就为审查其余的执行情况提供了所有必要的信息。
我审查了授予和撤销的权限,对预言机的任何改变,以及数字参数是否与讨论和同意的内容一致。
审查所有事件
我在Notion上记录我的审查,并公开提供每一个变化。其他治理这可以审查我的工作或以他们选择的任何其他方式进行自己的尽职调查。在任何情况下,我相信,如果我歪曲一个变化,肯定有足够高的风险被发现。
Gnosis Safe现在提供了一个批处理生成器,并自动在Tenderly上为所有执行创建一个模拟。这和我描述的功能是一样的,它有一个更友好的界面。
如果你的治理建议简单到可以在网络界面上手动创建,Gnosis Safe将是一个更简单的选择。你可以从Gnosis Safe共享模拟报告,并达到同样的效果。
Gnosis Safe提供了一个整洁的包装解决方案
区块链应用的治理是困难的,作为治理者,你有可能签署可能导致应用程序失败的变化,无论是错误还是设计。最近的事件只是强调了保护我们的用户免受伤害的必要性。
通过使用开发者工具和遵循Yield的简单流程,我们已经能够为治理者提供关于他们正在签署的内容的明确信息。我们应用文本提到的技术,已经成功地执行了一百多个独立的变化,总共有数千个合约调用,减少了治理错误的数量,并且能够比以前更早地发现错误。
我们所做的一切都不是魔术,它只是一种常识。请确保你理解你所签署的东西,如果必须的话,请要求提供必要的工具。这是对我们的用户负有责任。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!