深入了解智能合约的最小代理“EIP-1167”

현재 언어 번역이 없어 원문을 표시합니다.
概述在一开始,代理合约很难去掌握,在这篇文章中,我们将分析最小代理标准或“EIP-1167”,并创建一个代码示例。在开始之前,不要混淆可升级代理和最小代理,这是非常重要的,它们是完全不同的。在这篇文章中,我们只讨论最小代理。最小代理标准早在2018年就正式发布了,这个标准的主要思想是尽可能便宜地

概述

在一开始,代理合约很难去掌握,在这篇文章中,我们将分析最小代理标准或“EIP-1167”,并创建一个代码示例。

在开始之前,不要混淆可升级代理和最小代理,这是非常重要的,它们是完全不同的。在这篇文章中,我们只讨论最小代理。

最小代理标准早在2018年就正式发布了,这个标准的主要思想是尽可能便宜地部署基础合约的副本。让我进一步展开:

最好的例子是一个多重签名钱包。让我们假设用户创建了一个非常简单的多重签名钱包,它可以接收资金、发送资金和设置n个所有者的数量。当然,为了发送资金,用户需要达到一定的法定人数(n≥m)。一旦智能合约准备好了,有两种主要的方法可以让它准备好投入生产。第一个是部署合约,使所有用户直接交互,这意味着所有的资金将存储在该合约,为了跟踪谁拥有什么,需要创建一个映射(地址 => uint) 公共余额+ 一些修饰符。这种方法的问题在于,用户将所有内容集中在一个地方,最重要的是,用户打开了额外攻击向量的可能性。换句话说,用户使合约更加复杂了。我们不想这样做,我们想把安全作为我们的首要任务。想到的第二种方法是让用户部署合约,为了做到这一点,用户需要编译合约,将字节码放在前端,并让用户部署合约。这种方法的问题是效率非常低,而且gas价格昂贵。想象一下,如果合约变得太大太复杂,部署成本将非常高,再加上我们正在用大量存储轰炸链。解决这个问题的方法是实现最小代理标准。

最小代理所做的是创建一个廉价的克隆(我们称它为廉价,因为它的部署成本非常低),它具有与实现合约完全相同的逻辑,但具有自己的存储状态。这是通过低级别的委托调用实现的。

为了实施这个标准,我们需要:

执行合约:有时被称为基础合约、核心合约、主合约等。重要的是,执行合约是所有逻辑所在的地方。代理工厂或克隆工厂:顾名思义,克隆工厂合约将是我们的工厂。这意味着用户将调用工厂的一个函数,而工厂将克隆一份实施合同的精确副本,但拥有自己的存储空间。这意味着每个克隆都有相同的逻辑,但存储状态独立。代理:如前所述,代理合约是实施合约的克隆,但具有独特的存储。

现在我们有了一个大致的了解,让我们创建一个示例来巩固我们的知识。我们将在Remix中做这个例子,使它更简单。

合约将会非常简单,这里的目的是理解标准。

为此我们需要以下合约:

实现:这是我们的逻辑所在的地方,我们将其称为Implementation.sol。CloneFactory:这将是我们的工厂,我们将有一个clone() 函数,用户将触发该函数,工厂将输出代理的地址。工厂的名称将是CloneFactory.sol。代理:与代理无关,代理将是CloneFactory.sol 中的clone() 函数的输出。可以有尽可能多的不同代理,这就是整个目的,以创建许多Implementation.sol的克隆。

这是它看起来的样子:

需要记住的一个非常重要的方面是,克隆不知道构造函数,因此我们使用initialize()函数“替换构造函数”,而不是使用构造函数来分配重要变量。我们只需要确保initialize()函数只被调用一次,这样人们就不能篡改合约,类似于构造函数的工作原理。为了做到这一点,我们通常使用openZeppelin的Initializabl。对于本例,我们不打算使用任何第三方合约,只是为了更清楚地说明。

让我们从Implementation.sol开始。合约唯一要做的就是拥有一个带有setter函数和修饰符的uint公共变量,限制只有所有者才能更改变量的访问权限。

让我们来分析一下:

uint public x→我们将在setter函数中更改的无符号整数(默认为0)。

bool public isBase→这个布尔值将确保实现合约永远不会被初始化。如果在构造函数中看到,我们将:isBase设置为true,并且initialize()函数的第一个require语句是require(isBase ==false)。这保证了实现合约只用于逻辑,没有人可以篡改。请记住,代理或克隆合约不知道构造函数,因此isBase将被设置为其默认值false。

address public owner→合约所有者(外部拥有的账户)。所有者默认为address(0)。在Solidity中,如果不分配地址类型,则默认值为address(0)。

modifier onlyOwner()→希望用户不需要解释这个,但基本上这是说只有所有者可以调用这个函数。

initialize(address _owner)→一旦创建代理克隆,需要立即调用initialize函数。这就像我们的构造函数,意味着如果之前有人调用这个函数,它将控制合约。如我们所见,它有一个参数(address _owner)。该参数将在CloneFactory 中提供。这里有两个重要的考虑:

用户需要确保initialize函数只被调用一次。我们这样做,是通过检查所有者是否是地址(0)。一旦分配了所有者,并且我们试图再次调用该函数,交易将恢复。强烈建议在Initializable合约中使用这个体系结构+ OpenZppelin的initializer()修饰符。这确保了函数只能被调用一次。使实现合约不可用:通过在构造函数中赋值isBase=true,并在initialize()函数中要求isBase== false,我们可以确保没有人可以篡改合约。该合约的唯一目的是充当逻辑合约,如果有人试图调用基础合约的初始化函数,它将立即恢复。

一旦我们准备好了Implementation.sol,让我们创建CloneFactory.sol。为此,我们将使用 OpenZeppelin 的 Clone 库中的 clone() 函数。

让我们来分析一下:

interface Implementation→initialize()是我们在Implementation.sol中需要的唯一函数。一旦创建了克隆合约,我们将立即调用它。

address public implementation→implementation .sol的地址。

mapping(address => address[]) public allClone →这只是一个跟踪所有已部署克隆的映射,第一个地址是 msg.sender 或克隆的所有者。

clon(address implementation)→这个函数来自Open Zeppelin。在较高的层次上,我们提供实现地址(implementation .sol),它返回该地址的一个实例,换句话说,它返回一个完全相同的implementation .sol的克隆。

clone()→这是用户将要调用的函数。一旦有人调用这个函数,第一件事就是创建一个新的克隆并将它保存在地址 sameChild 下。这个地址将持有与Implementation.sol相同的逻辑,但有自己的存储状态。正如我们在clone()的第三行中看到的,我们正在调用initialize函数:

这是至关重要的,这就是为什么我们要立即调用它。这将使clone()函数的调用者成为克隆合约的所有者。一旦做了,就没有回头路了。

用户可以克隆任意数量的合约,每个合约都有自己的存储空间。任何调用_clone()函数的人都将是唯一能够访问更改“x”的帐户。

结论

当我们需要为一个合约创建多个副本时,最小代理标准非常有效,每个副本都有自己的存储状态。最常见的用例是:Multi-Sig-Wallet、托管账户、某种类型的流动性池等。

作为提醒,无论何时执行此标准,都要特别注意以下事项:

实现合约中的initialize()函数只能被调用一次。需要尽快触发实现合约中的initialize()函数。使实现合约不可用。

Source:https://medium.com/coinmonks/diving-into-smart-contracts-minimal-proxy-eip-1167-3c4e7f1a41b8

공유하기:

작성자: 去中心化金融社区

이 글은 PANews 입주 칼럼니스트의 관점으로, PANews의 입장을 대표하지 않으며 법적 책임을 지지 않습니다.

글 및 관점은 투자 조언을 구성하지 않습니다

이미지 출처: 去中心化金融社区. 권리 침해가 있을 경우 저자에게 삭제를 요청해 주세요.

PANews 공식 계정을 팔로우하고 함께 상승장과 하락장을 헤쳐나가세요
PANews APP
레인저 파이낸스는 자금난과 드리프트 공격의 여파로 인해 단계적으로 사업을 종료할 것이라고 발표했습니다.
PANews 속보