本文档介绍了 OpenZeppelin Contracts 库中关于代理模式的各种实现,包括 Proxy
、ERC1967Proxy
、TransparentUpgradeableProxy
、UUPSUpgradeable
、BeaconProxy
和 Clones
等合约,以及它们的使用场景、升级机制和安全性考虑。
此文档在 https://docs.openzeppelin.com/contracts/api/proxy 上查看效果更佳 |
这是一组底层合约,实现了具有和不具有可升级性的不同代理模式。有关此模式的深入概述,请查看 代理升级模式 页面。
以下大多数代理都建立在一个抽象的基础合约上。
Proxy
: 抽象合约,实现了核心委托功能。为了避免与代理后面的实现合约的存储变量发生冲突,我们使用 ERC-1967 存储插槽。
ERC1967Utils
: 内部函数,用于获取和设置 ERC-1967 中定义的存储插槽。
ERC1967Proxy
: 使用 ERC-1967 存储插槽的代理。默认情况下不可升级。
有两种替代方法可以向 ERC-1967 代理添加可升级性。它们的区别在下面的 Transparent vs UUPS Proxies 中进行了解释。
TransparentUpgradeableProxy
: 具有内置的不可变管理员和升级接口的代理。
UUPSUpgradeable
: 要包含在实现合约中的可升级性机制。
正确且安全地使用可升级代理是一项艰巨的任务,需要深入了解代理模式、Solidity 和 EVM。除非你想要大量的底层控制,否则我们建议使用 Hardhat 和 Foundry 的 OpenZeppelin Upgrades Plugins。 |
另一类不同的代理是信标代理 (beacon proxies)。这种由 Dharma 推广的模式允许将多个代理在单个交易中升级到不同的实现。
BeaconProxy
: 一种从信标合约检索其实现的代理。
UpgradeableBeacon
: 一种内置管理员的信标合约,可以升级指向它的 BeaconProxy
。
在这种模式中,代理合约不像 ERC-1967 代理那样将实现地址存储在存储中。相反,该地址存储在单独的信标合约中。upgrade
操作被发送到信标而不是代理合约,并且所有遵循该信标的代理都会自动升级。
在可升级性之外,代理还有助于制作廉价的合约克隆,例如由链上工厂合约创建的合约克隆,该合约创建同一合约的多个实例。这些实例被设计为部署成本和调用成本都很低。
Clones
: 一个可以部署廉价的最小不可升级代理的库。OpenZeppelin 中最初包含的代理遵循 Transparent Proxy Pattern。虽然仍然提供此模式,但我们现在的建议正在转向 UUPS 代理,后者既轻量级又通用。UUPS 这个名称来自 ERC-1822,该标准首次记录了该模式。
虽然两者都共享相同的升级接口,但在 UUPS 代理中,升级由实现处理,并且最终可以删除。另一方面,透明代理在代理本身中包含升级和管理逻辑。这意味着 TransparentUpgradeableProxy
的部署成本高于 UUPS 代理的可能成本。
UUPS 代理是使用 ERC1967Proxy
实现的。请注意,此代理本身不可升级。实现的职责是与合约的逻辑一起包含更新存储在代理的存储空间中特定插槽中的实现地址所需的所有代码。这就是 UUPSUpgradeable
合约的用武之地。从它继承 (并使用相关的访问控制机制覆盖 _authorizeUpgrade
函数) 会将你的合约变成符合 UUPS 的实现。
请注意,由于两个代理都使用相同的存储插槽来存储实现地址,因此将符合 UUPS 的实现与 TransparentUpgradeableProxy
结合使用可能会允许非管理员执行升级操作。
默认情况下,UUPSUpgradeable
中包含的升级功能包含一个安全机制,该机制将阻止对非 UUPS 兼容实现的任何升级。这可以防止升级到不包含必要升级机制的实现合约,因为它会永久锁定代理的可升级性。可以通过以下任一方式绕过此安全机制:
在实现中添加一个标志机制,该机制将在触发时禁用升级功能。
升级到具有不含额外安全检查的升级机制的实现,然后再次升级到另一个不含升级机制的实现。
此安全机制的当前实现使用 ERC-1822 来检测实现使用的存储插槽。先前的一个实现 (现已弃用) 依赖于回滚检查。可以从使用旧机制的合约升级到新机制。但是,反过来是不可能的,因为旧实现 (4.5 版本之前) 不包含 ERC-1822 接口。
Proxy
import "@openzeppelin/contracts/proxy/Proxy.sol";
此抽象合约提供了一个 fallback 函数,该函数使用 EVM 指令 delegatecall
将所有调用委托给另一个合约。我们将第二个合约称为代理后面的 实现,它必须通过覆盖 virtual 函数 _implementation
来指定。
此外,可以通过 _fallback
函数手动触发到实现的委托,或者通过 _delegate
函数触发到不同合约的委托。
委托调用的成功和返回数据将返回给代理的调用者。
函数
_delegate(address implementation)
内部将当前调用委托给 implementation
。
此函数不会返回到其内部调用站点,它将直接返回到外部调用者。
_implementation() → address
内部这是一个虚函数,应该被覆盖,以便它返回 fallback 函数和 _fallback
应该委托到的地址。
_fallback()
内部将当前调用委托给 _implementation()
返回的地址。
此函数不会返回到其内部调用站点,它将直接返回到外部调用者。
fallback()
外部将调用委托给 _implementation()
返回的地址的 Fallback 函数。如果没有其他函数与合约中的调用数据匹配,将运行此函数。
ERC1967Proxy
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
此合约实现了一个可升级的代理。它是可升级的,因为调用被委托给一个可以更改的实现地址。此地址存储在 ERC-1967 指定的位置的存储中,因此它不会与代理后面的实现的存储布局冲突。
函数
代理
constructor(address implementation, bytes _data)
public使用 implementation
指定的初始实现初始化可升级代理。
如果 _data
非空,它将用作委托调用 implementation
中的数据。这通常是一个编码的函数调用,并允许像 Solidity 构造函数一样初始化代理的存储。
要求:
data
为空,则 msg.value
必须为零。_implementation() → address
内部返回当前的实现地址。
为了获得此值,客户端可以使用 eth_getStorageAt RPC 调用直接从下面显示的存储插槽 (由 ERC-1967 指定) 读取。<br>0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc |
ERC1967Utils
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
此库为 ERC-1967 插槽提供了 getter 和事件发送更新函数。
函数
错误
内部变量
getImplementation() → address
内部返回当前的实现地址。
upgradeToAndCall(address newImplementation, bytes data)
内部执行实现升级,如果数据非空,则执行额外的设置调用。
仅当执行设置调用时,此函数才是 payable 的,否则将拒绝 msg.value
以避免合约中出现卡住的值。
发送一个 IERC1967.Upgraded
事件。
getAdmin() → address
内部返回当前的管理员。
为了获得此值,客户端可以使用 eth_getStorageAt RPC 调用直接从下面显示的存储插槽 (由 ERC-1967 指定) 读取。<br>0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 |
changeAdmin(address newAdmin)
内部更改代理的管理员。
发送一个 IERC1967.AdminChanged
事件。
getBeacon() → address
内部返回当前的信标。
upgradeBeaconToAndCall(address newBeacon, bytes data)
内部更改信标,如果数据非空,则触发设置调用。
仅当执行设置调用时,此函数才是 payable 的,否则将拒绝 msg.value
以避免合约中出现卡住的值。
发送一个 IERC1967.BeaconUpgraded
事件。
由于 v5 以来,调用此函数对 BeaconProxy 的实例没有影响,因为它使用不可变的信标而不查看 ERC-1967 信标插槽的值以提高效率。 |
ERC1967InvalidImplementation(address implementation)
error代理的 implementation
无效。
ERC1967InvalidAdmin(address admin)
error代理的 admin
无效。
ERC1967InvalidBeacon(address beacon)
error代理的 beacon
无效。
ERC1967NonPayable()
error升级函数看到 msg.value > 0
可能会丢失。
bytes32 IMPLEMENTATION_SLOT
内部 constant存储当前实现的地址的插槽。 这是 "eip1967.proxy.implementation" 的 keccak-256 哈希值减 1。
bytes32 ADMIN_SLOT
内部 constant存储合约管理员的插槽。 这是 "eip1967.proxy.admin" 的 keccak-256 哈希值减 1。
bytes32 BEACON_SLOT
内部 constantUpgradeableBeacon 合约的存储插槽,该合约定义了此代理的实现。 这是 "eip1967.proxy.beacon" 的 keccak-256 哈希值减 1。
TransparentUpgradeableProxy
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
此合约实现了一个可通过关联的 ProxyAdmin
实例升级的代理。
为了避免 代理选择器冲突,这可能会在攻击中使用,此合约使用 透明代理模式。此模式意味着两件事,这两件事是相辅相成的:
如果除管理员以外的任何帐户调用代理,则该调用将转发到实现,即使该调用与代理本身公开的 ITransparentUpgradeableProxy.upgradeToAndCall
函数匹配也是如此。
如果管理员调用代理,它可以调用 upgradeToAndCall
函数,但任何其他调用都不会转发到实现。如果管理员尝试调用实现中的函数,它将失败并显示一条错误,指示代理管理员无法 fallback 到目标实现。
这些属性意味着管理员帐户只能用于升级代理,因此最好将其作为不用于其他任何用途的专用帐户。这将避免因尝试从代理实现中调用函数时突然出现错误而造成的麻烦。因此,代理部署了一个 ProxyAdmin
实例,并且仅当它们通过它时才允许升级。你应该将 ProxyAdmin
实例视为代理的管理接口,包括通过转移所有权来更改可以触发升级的人员的能力。
此代理的真实接口是在 ITransparentUpgradeableProxy 中定义的接口。此合约不从该接口继承,而是使用 _fallback 中的自定义调度机制隐式实现 upgradeToAndCall 。因此,编译器不会为此合约生成 ABI。这是完全实现透明性所必需的,而不会解码由代理和实现之间的选择器冲突导致的回滚。 |
此代理不会从 Context 继承。此合约的 ProxyAdmin 不会以任何方式发送元事务,并且任何其他元事务设置都应在实现合约中进行。 |
此合约通过仅在构造期间将管理员设置为不可变变量来避免不必要的存储读取,从而防止此后的任何更改。但是,ERC-1967 中定义的管理员插槽仍然可以被此代理指向的实现逻辑覆盖。在这种情况下,合约最终可能会处于管理员插槽与实际管理员不同的不希望状态。如果实现是受信任的,则通常可以依赖管理员插槽的值。 |
不建议扩展此合约以添加其他外部函数。如果你这样做,编译器将不会检查是否存在选择器冲突,因为上面的说明。任何新函数与 ITransparentUpgradeableProxy 中声明的函数之间的选择器冲突将有利于新函数。这可能会使 upgradeToAndCall 函数无法访问,从而阻止可升级性并损害透明性。 |
函数
ERC1967Proxy
代理
错误
constructor(address _logic, address initialOwner, bytes _data)
public初始化一个由 ProxyAdmin
实例管理的、具有 initialOwner
的可升级代理,该代理由 _logic
处的实现支持,并且可以选择使用 _data
进行初始化,如 ERC1967Proxy.constructor
中所述。
_proxyAdmin() → address
内部返回此代理的管理员。
_fallback()
内部如果调用者是管理员,则在内部处理调用,否则以透明方式 fallback 到代理行为。
ProxyDeniedAdminAccess()
error代理调用者是当前管理员,无法 fallback 到代理目标。
ProxyAdmin
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
这是一个辅助合约,旨在分配为 TransparentUpgradeableProxy
的管理员。有关为什么要使用此合约的说明,请参阅 TransparentUpgradeableProxy
的文档。
函数
Ownable
事件
Ownable
错误
Ownable
constructor(address initialOwner)
public设置可以执行升级的初始所有者。
upgradeAndCall(contract ITransparentUpgradeableProxy proxy, address implementation, bytes data)
public将 proxy
升级到 implementation
并调用新实现中的函数。
请参阅 TransparentUpgradeableProxy._dispatchUpgradeToAndCall
。
要求:
此合约必须是 proxy
的管理员。
如果 data
为空,则 msg.value
必须为零。
UPGRADE_INTERFACE_VERSION() → string
public合约的升级接口的版本。如果缺少此 getter,则 upgrade(address,address)
和 upgradeAndCall(address,address,bytes)
都存在,并且如果没有调用任何函数,则必须使用 upgrade
,而如果第三个参数是空字节字符串,则 upgradeAndCall
将调用 receive
函数。如果 getter 返回 "5.0.0"
,则只有 upgradeAndCall(address,address,bytes)
存在,如果没有调用任何函数,则第三个参数必须是空字节字符串,因此无法在升级期间调用 receive
函数。
BeaconProxy
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
此合约实现了一个代理,该代理从 UpgradeableBeacon
获取每次调用的实现地址。
信标地址只能在构造期间设置一次,之后不能更改。它存储在一个不可变变量中以避免不必要的存储读取,并且还存储在 ERC-1967 指定的信标存储插槽中,以便可以从外部访问它。
由于信标地址永远无法更改,因此你必须确保你控制信标,或者信任信标不会恶意升级实现。 |
不要使用实现逻辑来修改信标存储插槽。这样做会将代理置于信标存储插槽与信标地址不匹配的不一致状态。 |
函数
代理
constructor(address beacon, bytes data)
public使用 beacon
初始化代理。
如果 data
非空,则将其用作委托调用信标返回的实现中的数据。这通常是一个编码的函数调用,并允许像 Solidity 构造函数一样初始化代理的存储。
要求:
beacon
必须是具有 IBeacon
接口的合约。
如果 data
为空,则 msg.value
必须为零。
_implementation() → address
内部返回关联信标的当前实现地址。
_getBeacon() → address
内部返回信标。
IBeacon
import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
这是 BeaconProxy
对其信标的期望的接口。
函数
implementation() → address
外部必须返回可以用作委托调用目标的地址。
UpgradeableBeacon
将检查此地址是否是合约。
UpgradeableBeacon
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
此合约与一个或多个 BeaconProxy
实例结合使用,以确定它们的实现合约,这是它们将委托所有函数调用的地方。
所有者可以更改信标指向的实现,从而升级使用此信标的代理。
函数
Ownable
事件
Ownable
错误
Ownable
constructor(address implementation_, address initialOwner)
public设置初始实现的地址和可以升级信标的初始所有者。
implementation() → address
public返回当前的实现地址。
upgradeTo(address newImplementation)
public将信标升级到新的实现。
发送一个 Upgraded
事件。
要求:
msg.sender 必须是合约的所有者。
newImplementation
必须是合约。
Upgraded(address indexed implementation)
event当信标返回的实现更改时发送。
BeaconInvalidImplementation(address implementation)
error信标的 implementation
无效。
Clones
import "@openzeppelin/contracts/proxy/Clones.sol";
ERC-1167 是部署最小代理合约 (也称为“克隆”) 的标准。
为了以不可变的方式简单且廉价地克隆合约功能,此标准指定了一个最小字节码实现,该实现将所有调用委托给已知的固定地址。
该库包括使用 create
(传统部署) 或 create2
(加盐确定性部署) 部署代理的函数。它还包括预测使用确定性方法部署的克隆的地址的函数。
函数
cloneDeterministicWithImmutableArgs(implementation, args, salt)
[cloneDeterministicWithImmutableArgs(implementation, args, salt, value) ](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint2 |
||
---|---|---|
在创建时使用非零值将要求使用此功能的合约(例如,工厂)<br>始终有足够的余额来进行新的部署。 考虑在 payable 方法下公开此函数。 |
predictDeterministicAddress(address implementation, bytes32 salt, address deployer) → address predicted
internal计算使用 Clones.cloneDeterministic
部署的克隆的地址。
predictDeterministicAddress(address implementation, bytes32 salt) → address predicted
internal计算使用 Clones.cloneDeterministic
部署的克隆的地址。
cloneWithImmutableArgs(address implementation, bytes args) → address instance
internal部署并返回克隆的地址,该克隆模仿具有自定义 不可变 参数的 implementation
的行为。 这些参数通过 args
提供,并且在部署后无法更改。 要访问实现中的参数,请使用 fetchCloneArgs
。
此函数使用 create 操作码,该操作码永远不会恢复。
cloneWithImmutableArgs(address implementation, bytes args, uint256 value) → address instance
internal与 cloneWithImmutableArgs 相同,但带有 value
参数,用于将原生货币发送到新合约。
在创建时使用非零值将要求使用此功能的合约(例如,工厂)<br>始终有足够的余额来进行新的部署。 考虑在 payable 方法下公开此函数。 |
cloneDeterministicWithImmutableArgs(address implementation, bytes args, bytes32 salt) → address instance
internal部署并返回克隆的地址,该克隆模仿具有自定义 不可变 参数的 implementation
的行为。 这些参数通过 args
提供,并且在部署后无法更改。 要访问实现中的参数,请使用 fetchCloneArgs
。
此函数使用 create2 操作码和一个 salt
来确定性地部署克隆。 多次使用相同的 implementation
、args
和 salt
将恢复,因为克隆不能在同一地址部署两次。
cloneDeterministicWithImmutableArgs(address implementation, bytes args, bytes32 salt, uint256 value) → address instance
internal与 cloneDeterministicWithImmutableArgs 相同,但带有 value
参数,用于将原生货币发送到新合约。
在创建时使用非零值将要求使用此功能的合约(例如,工厂)<br>始终有足够的余额来进行新的部署。 考虑在 payable 方法下公开此函数。 |
predictDeterministicAddressWithImmutableArgs(address implementation, bytes args, bytes32 salt, address deployer) → address predicted
internal计算使用 Clones.cloneDeterministicWithImmutableArgs
部署的克隆的地址。
predictDeterministicAddressWithImmutableArgs(address implementation, bytes args, bytes32 salt) → address predicted
internal计算使用 Clones.cloneDeterministicWithImmutableArgs
部署的克隆的地址。
fetchCloneArgs(address instance) → bytes
internal获取附加到克隆的 不可变 参数。
如果 instance
是使用 clone
或 cloneDeterministic
部署的克隆,则此函数将返回一个空数组。
如果 instance
是使用 cloneWithImmutableArgs
或 cloneDeterministicWithImmutableArgs
部署的克隆,则此函数将返回创建时使用的 args 数组。
如果 instance
不是 使用此库部署的克隆,则行为未定义。 此函数应仅用于检查已知为克隆的地址。
CloneArgumentsTooLong()
errorInitializable
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
这是一个基础合约,用于帮助编写可升级合约,或任何将部署在代理后面的合约。 由于代理合约不使用构造函数,因此通常将构造函数逻辑移动到外部初始化函数,通常称为 initialize
。 然后,有必要保护此初始化函数,使其只能被调用一次。 此合约提供的 initializer
修饰符将具有此效果。
初始化函数使用版本号。 一旦使用版本号,它就会被消耗掉,并且不能重复使用。 这种机制可以防止重复执行每个 “步骤”,但允许在升级添加需要初始化的模块时创建新的初始化步骤。
例如:
contract MyToken is ERC20Upgradeable {
function initialize() initializer public {
__ERC20_init("MyToken", "MTK");
}
}
contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
function initializeV2() reinitializer(2) public {
__ERC20Permit_init("MyToken");
}
}
为了避免将代理置于未初始化的状态,应尽早调用初始化函数,方法是将编码的函数调用作为 _data 参数提供给 ERC1967Proxy.constructor 。 |
当与继承一起使用时,必须手动小心,不要两次调用父初始化程序,或确保所有初始化程序都是 幂等 的。 这不会像 Solidity 那样自动验证构造函数。 |
避免使合约未初始化。<br>未**初始化的合约可能被攻击者接管。 这既适用于代理及其实现<br>合约,这可能会影响代理。 为了防止使用实现合约,你应该在构造函数中调用 _disableInitializers 函数,以便在部署时自动锁定它:<br>none hljs<br>/// @custom:oz-upgrades-unsafe-allow constructor<br>constructor() {<br> _disableInitializers();<br>}<br> |
修饰符
函数
事件
错误
initializer()
modifier一个修饰符,用于定义一个受保护的初始化函数,该函数最多可以调用一次。 在其范围内,onlyInitializing
函数可用于初始化父合约。
与 reinitializer(1)
类似,不同之处在于,在构造函数的上下文中,initializer
可以被多次调用。 构造函数中的这种行为在测试期间可能很有用,并且预计不会在生产中使用。
发出一个 Initialized
事件。
reinitializer(uint64 version)
modifier一个修饰符,用于定义一个受保护的重新初始化函数,该函数最多可以调用一次,并且只有在合约尚未初始化为更大的版本之前才能调用。 在其范围内,onlyInitializing
函数可用于初始化父合约。
在原始初始化步骤之后可以使用重新初始化程序。 这对于配置通过升级添加并且需要初始化的模块至关重要。
当 version
为 1 时,此修饰符与 initializer
类似,不同之处在于标有 reinitializer
的函数不能嵌套。 如果在一个函数的上下文中调用另一个函数,则执行将恢复。
请注意,版本可以以大于 1 的增量跳跃; 这意味着,如果多个重新初始化程序共存于一个合约中,则以正确的顺序执行它们取决于开发人员或运营商。
将版本设置为 2**64 - 1 将阻止任何未来的重新初始化。 |
发出一个 Initialized
事件。
onlyInitializing()
modifier修饰符,用于保护初始化函数,使其只能由具有 initializer
和 reinitializer
修饰符的函数直接或间接调用。
_checkInitializing()
internal如果合约未处于初始化状态,则恢复。 请参阅 onlyInitializing
。
_disableInitializers()
internal锁定合约,防止任何未来的重新初始化。 这不能是初始化调用的一部分。 在合约的构造函数中调用此函数将阻止该合约初始化或重新初始化为任何版本。 建议使用此方法锁定设计为通过代理调用的实现合约。
首次成功执行时,会触发一个 Initialized
事件。
_getInitializedVersion() → uint64
internal返回已初始化的最高版本。 请参阅 reinitializer
。
_isInitializing() → bool
internal如果合约当前正在初始化,则返回 true
。 请参阅 onlyInitializing
。
_initializableStorageSlot() → bytes32
internal指向存储槽的指针。 允许集成者使用自定义存储位置覆盖它。
考虑遵循 ERC-7201 公式来推导存储位置。 |
Initialized(uint64 version)
event当合约已初始化或重新初始化时触发。
InvalidInitialization()
error合约已初始化。
NotInitializing()
error合约未初始化。
UUPSUpgradeable
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
一种专为 UUPS 代理设计的可升级性机制。 此处包含的函数可以在 ERC1967Proxy
是这种代理后面的实现时,执行此代理的升级。
一种安全机制可确保升级不会意外关闭可升级性,但是,如果升级保留可升级性但删除了安全机制,则会恢复此风险,例如,通过将 UUPSUpgradeable
替换为升级的自定义实现。
必须覆盖 _authorizeUpgrade
函数,以包括对升级机制的访问限制。
修饰符
函数
错误
onlyProxy()
modifier检查执行是否通过委托调用执行,以及执行上下文是否是指向自身的具有 ERC-1967 兼容实现的代理合约。 这应该仅适用于使用当前合约作为其实现的 UUPS 和透明代理。 通过 ERC-1167 最小 代理(克隆)执行函数通常不会通过此测试,但不保证失败。
notDelegated()
modifier检查执行是否未通过委托调用执行。 这允许在实现合约上调用函数,但不能通过代理调用。
proxiableUUID() → bytes32
externalERC-1822 proxiableUUID
函数的实现。 这将返回实现使用的存储槽。 它用于在执行升级时验证实现的兼容性。
指向可代理合约的代理本身不应被视为可代理,因为这可能会冒着通过代理升级到它的风险,方法是委托给自己直到耗尽 gas。 因此,至关重要的是,如果通过代理调用此<br>函数,则该函数会恢复。 notDelegated 修饰符保证了这一点。 |
upgradeToAndCall(address newImplementation, bytes data)
public将代理的实现升级到 newImplementation
,然后执行以 data
编码的函数调用。
发出一个 Upgraded
事件。
_checkProxy()
internal如果执行不是通过委托调用执行的,或者执行上下文不是具有指向自身的 ERC-1967 兼容实现的代理,则恢复。
_checkNotDelegated()
internal如果执行是通过委托调用执行的,则恢复。
请参阅 notDelegated
。
_authorizeUpgrade(address newImplementation)
internal当 msg.sender
无权升级合约时应恢复的函数。 由 upgradeToAndCall
调用。
通常,此函数将使用 访问控制 修饰符,例如 Ownable.onlyOwner
。
function _authorizeUpgrade(address) internal onlyOwner {}
UPGRADE_INTERFACE_VERSION() → string
public合约的升级接口的版本。 如果缺少此 getter,则 upgradeTo(address)
和 upgradeToAndCall(address,bytes)
都存在,并且如果没有函数应该被调用,则必须使用 upgradeTo
,而如果第二个参数是空字节字符串,则 upgradeToAndCall
将调用 receive
函数。 如果 getter 返回 "5.0.0"
,则仅存在 upgradeToAndCall(address,bytes)
,并且如果没有函数应该被调用,则第二个参数必须是空字节字符串,这使得在升级期间无法调用 receive
函数。
UUPSUnauthorizedCallContext()
error调用来自未经授权的上下文。
UUPSUnsupportedProxiableUUID(bytes32 slot)
error存储 slot
不支持作为 UUID。
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!