区块链研究实验室|如何使用OpenZeppelin的新AccessControl合约

  • 时间:
  • 浏览:57
  • 来源:区块链技术网

原文作者:Alex Roan

OpenZeppelin的智能合约库版本3已经发布!在最新版本中,他们引入了一种全新的控制功能访问的方法。

控制某些功能的访问权限对于确保智能合约的安全性至关重要,而自从以太坊虚拟机问世以来,Solidity就是如此。

熟悉OpenZeppelin的智能合约存储库的开发人员知道,它已经提供了根据访问级别限制功能的选项。

最常见的是由所有者Ownable合约管理的onlyOwner模式。另一个是Openzeppelin的“roles”合约,该合约使合约可以在部署之前定义多个角色并在每个功能中设置规则,以确保msg.sender担任正确的角色。

Ownable

onlyOwner模式是最常用且易于实现的访问控制方法。它是原始的但非常有效。

它假定智能合约只有一个管理员,并允许管理员将所有权转移到另一个地址。

扩展Ownable合约允许子合约使用onlyOwner自定义修饰符定义功能。这些功能要求事务的发送者是单一管理员。

functionnormalFunction()public{//anyonecancallthis}functionrestrictedFunction()publiconlyOwner{//onlytheownercancallthis}

这是一个简单的示例,说明如何利用Ownable合约提供的自定义修饰符来限制功能访问。

#### Roles

尽管Ownable合约很受欢迎且易于使用,但存储库中的其他OpenZeppelin合约仅使用Roles库进行访问控制。这是因为Roles库在Ownable合约的刚性方面提供了灵活性。

作为一个库,它不会由子合约扩展,而是通过using语句用作为数据类型添加功能的工具。Roles库为它定义的role数据类型提供了三个功能。

代码显示了Roles的定义。

pragmasolidity^0.5.0;/***@titleRoles*@devLibraryformanagingaddressesassignedtoaRole.*/libraryRoles{structRole{mapping(address=>bool)bearer;}/***@devGiveanaccountaccesstothisrole.*/functionadd(Rolestoragerole,addressaccount)internal{require(!has(role,account),"Roles:accountalreadyhasrole");role.bearer[account]=true;}/***@devRemoveanaccount'saccesstothisrole.*/functionremove(Rolestoragerole,addressaccount)internal{require(has(role,account),"Roles:accountdoesnothaverole");role.bearer[account]=false;}/***@devCheckifanaccounthasthisrole.*@returnbool*/functionhas(Rolestoragerole,addressaccount)internalviewreturns(bool){require(account!=address(0),"Roles:accountisthezeroaddress");returnrole.bearer[account];}}

在顶部,您可以看到Role结构。合约使用它来定义多个角色及其成员。函数add(),remove()和has()是库用于与Role结构交互的函数。

例如下段代码展示了代币如何使用两个单独的角色_minters和_burners来将访问限制应用于某些功能。

pragmasolidity^0.5.0;import"@openzeppelin/contracts/access/Roles.sol";import"@openzeppelin/contracts/token/ERC20/ERC20.sol";import"@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";contractMyTokenisERC20,ERC20Detailed{usingRolesforRoles.Role;Roles.Roleprivate_minters;Roles.Roleprivate_burners;constructor(address[]memoryminters,address[]memoryburners)ERC20Detailed("MyToken","MTKN",18)public{for(uint256i=0;i<minters.length;++i){_minters.add(minters[i]);}for(uint256i=0;i<burners.length;++i){_burners.add(burners[i]);}}functionmint(addressto,uint256amount)public{//Onlyminterscanmintrequire(_minters.has(msg.sender),"DOES_NOT_HAVE_MINTER_ROLE");_mint(to,amount);}functionburn(addressfrom,uint256amount)public{//Onlyburnerscanburnrequire(_burners.has(msg.sender),"DOES_NOT_HAVE_BURNER_ROLE");_burn(from,amount);}}

注意,在mint()函数中,require语句如何通过使用_minters.has(msg.sender)函数来确保消息的发件人是一个铸造者。

考虑到这已经是一段时间的标准,对于开发人员来说,最大的新闻是从2.5.x版本升级到3.x版本后,Roles合约已被删除。

Principles

Roles库对其提供的功能有所限制。

作为一个库,数据存储必须由导入合约控制。理想情况下,访问控制应该抽象到某种程度,而导入合约只需要担心对每个函数的限制。

新的AccessControl合同被吹捧为:

一站式服务,满足所有授权需求。它使您可以轻松定义具有不同权限的多个角色,以及允许哪些帐户授予和撤消每个角色。通过启用系统中所有特权帐户的枚举,还可以提高透明度。

该语句的最后两点对于Roles库是不可能的。

OpenZeppelin看起来正朝着一种使人们联想到传统计算安全性中突出的基于角色的访问控制(RBAC)和基于属性的访问控制(ABAC)标准的系统发展。

代码解析

展示AccessControl合约代码。

pragmasolidity^0.6.0;import"../utils/EnumerableSet.sol";import"../utils/Address.sol";import"../GSN/Context.sol";/***@devContractmodulethatallowschildrentoimplementrole-basedaccess*controlmechanisms.**Rolesarereferredtobytheir`bytes32`identifier.Theseshouldbeexposed*intheexternalAPIandbeunique.Thebestwaytoachievethisisby*using`publicconstant`hashdigests:**```*bytes32publicconstantMY_ROLE=keccak256("MY_ROLE");*```**Rolescanbeusedtorepresentasetofpermissions.Torestrictaccesstoa*functioncall,use{hasRole}:**```*functionfoo()public{*require(hasRole(MY_ROLE,_msgSender()));*...*}*```**Rolescanbegrantedandrevokeddynamicallyviathe{grantRole}and*{revokeRole}functions.Eachrolehasanassociatedadminrole,andonly*accountsthathavearole'sadminrolecancall{grantRole}and{revokeRole}.**Bydefault,theadminroleforallrolesis`DEFAULT_ADMIN_ROLE`,whichmeans*thatonlyaccountswiththisrolewillbeabletograntorrevokeother*roles.Morecomplexrolerelationshipscanbecreatedbyusing*{_setRoleAdmin}.*/abstractcontractAccessControlisContext{usingEnumerableSetforEnumerableSet.AddressSet;usingAddressforaddress;structRoleData{EnumerableSet.AddressSetmembers;bytes32adminRole;}mapping(bytes32=>RoleData)private_roles;bytes32publicconstantDEFAULT_ADMIN_ROLE=0x00;/***@devEmittedwhen`account`isgranted`role`.**`sender`istheaccountthatoriginatedthecontractcall,anadminrole*bearerexceptwhenusing{_setupRole}.*/eventRoleGranted(bytes32indexedrole,addressindexedaccount,addressindexedsender);/***@devEmittedwhen`account`isrevoked`role`.**`sender`istheaccountthatoriginatedthecontractcall:*-ifusing`revokeRole`,itistheadminrolebearer*-ifusing`renounceRole`,itistherolebearer(i.e.`account`)*/eventRoleRevoked(bytes32indexedrole,addressindexedaccount,addressindexedsender);/***@devReturns`true`if`account`hasbeengranted`role`.*/functionhasRole(bytes32role,addressaccount)publicviewreturns(bool){return_roles[role].members.contains(account);}/***@devReturnsthenumberofaccountsthathave`role`.Canbeused*togetherwith{getRoleMember}toenumerateallbearersofarole.*/functiongetRoleMemberCount(bytes32role)publicviewreturns(uint256){return_roles[role].members.length();}/***@devReturnsoneoftheaccountsthathave`role`.`index`mustbea*valuebetween0and{getRoleMemberCount},non-inclusive.**Rolebearersarenotsortedinanyparticularway,andtheirorderingmay*changeatanypoint.**WARNING:Whenusing{getRoleMember}and{getRoleMemberCount},makesure*youperformallqueriesonthesameblock.Seethefollowing*https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forumpost]*formoreinformation.*/functiongetRoleMember(bytes32role,uint256index)publicviewreturns(address){return_roles[role].members.at(index);}/***@devReturnstheadminrolethatcontrols`role`.See{grantRole}and*{revokeRole}.**Tochangearole'sadmin,use{_setRoleAdmin}.*/functiongetRoleAdmin(bytes32role)publicviewreturns(bytes32){return_roles[role].adminRole;}/***@devGrants`role`to`account`.**If`account`hadnotbeenalreadygranted`role`,emitsa{RoleGranted}*event.**Requirements:**-thecallermusthave``role``'sadminrole.*/functiongrantRole(bytes32role,addressaccount)publicvirtual{require(hasRole(_roles[role].adminRole,_msgSender()),"AccessControl:sendermustbeanadmintogrant");_grantRole(role,account);}/***@devRevokes`role`from`account`.**If`account`hadbeengranted`role`,emitsa{RoleRevoked}event.**Requirements:**-thecallermusthave``role``'sadminrole.*/functionrevokeRole(bytes32role,addressaccount)publicvirtual{require(hasRole(_roles[role].adminRole,_msgSender()),"AccessControl:sendermustbeanadmintorevoke");_revokeRole(role,account);}/***@devRevokes`role`fromthecallingaccount.**Rolesareoftenmanagedvia{grantRole}and{revokeRole}:thisfunction's*purposeistoprovideamechanismforaccountstolosetheirprivileges*iftheyarecompromised(suchaswhenatrusteddeviceismisplaced).**Ifthecallingaccounthadbeengranted`role`,emitsa{RoleRevoked}*event.**Requirements:**-thecallermustbe`account`.*/functionrenounceRole(bytes32role,addressaccount)publicvirtual{require(account==_msgSender(),"AccessControl:canonlyrenouncerolesforself");_revokeRole(role,account);}/***@devGrants`role`to`account`.**If`account`hadnotbeenalreadygranted`role`,emitsa{RoleGranted}*event.Notethatunlike{grantRole},thisfunctiondoesn'tperformany*checksonthecallingaccount.**[WARNING]*====*Thisfunctionshouldonlybecalledfromtheconstructorwhensetting*uptheinitialrolesforthesystem.**Usingthisfunctioninanyotherwayiseffectivelycircumventingtheadmin*systemimposedby{AccessControl}.*====*/function_setupRole(bytes32role,addressaccount)internalvirtual{_grantRole(role,account);}/***@devSets`adminRole`as``role``'sadminrole.*/function_setRoleAdmin(bytes32role,bytes32adminRole)internalvirtual{_roles[role].adminRole=adminRole;}function_grantRole(bytes32role,addressaccount)private{if(_roles[role].members.add(account)){emitRoleGranted(role,account,_msgSender());}}function_revokeRole(bytes32role,addressaccount)private{if(_roles[role].members.remove(account)){emitRoleRevoked(role,account,_msgSender());}}}

第42行上的Role Data结构使用EnumerableSet(也是版本3的新功能)作为存储成员的数据结构。这样可以轻松地对特权用户进行迭代。

该结构还将adminRole存储为bytes32变量。这定义了哪个角色充当特定角色的管理员(即该角色具有充当该角色的管理员,向用户授予和撤消该角色的能力)。

现在第57和66行中定义的角色被授予或撤消时,会发出事件。Roles合同仅提供三个功能:has(),add()和remove()。这些形式包括在AccessControl中以及额外的功能,例如获取角色计数,通过ID获取角色的特定成员以及放弃角色的能力。

如何使用它

第二段代码给出了使用Roles库的代币合约的示例,该合约需要两个单独的角色_minters和_burners。为了保持连续性,我们将使用相同的概念并应用AccessControl合约来做到这一点。

第四段代码显示了此实现。

pragmasolidity^0.6.0;import"@openzeppelin/contracts/access/AccessControl.sol";import"@openzeppelin/contracts/token/ERC20/ERC20.sol";contractMyTokenisERC20,AccessControl{bytes32publicconstantMINTER_ROLE=keccak256("MINTER_ROLE");bytes32publicconstantBURNER_ROLE=keccak256("BURNER_ROLE");constructor()publicERC20("MyToken","TKN"){//Grantthecontractdeployerthedefaultadminrole:itwillbeable//tograntandrevokeanyroles_setupRole(DEFAULT_ADMIN_ROLE,msg.sender);}functionmint(addressto,uint256amount)public{require(hasRole(MINTER_ROLE,msg.sender),"Callerisnotaminter");_mint(to,amount);}functionburn(addressfrom,uint256amount)public{require(hasRole(BURNER_ROLE,msg.sender),"Callerisnotaburner");_burn(from,amount);}}

那么发生了什么变化?首先每个角色不再在子合约中定义,因为它们存储在父合约中。

在子协定中,只有bytes32 ID作为常量状态变量存在(在本示例中为MINTER_ROLE和BURNER_ROLE)。

_setupRole()用于构造函数中,以设置角色的初始管理员,从而绕过AccessControl中grantRole()执行的检查(因为在构造时还没有管理员)。

此外函数不是将库函数作为数据类型的扩展名(即_minters.has(msg.sender)),而是本身具有内部函数(hasRole(MINTER_ROLE,msg.sender))。这使得子合约中的代码通常更清晰易读。

与Roles库相比,抽象出更多功能可以使子合约更容易在AccessControl合约之上构建。

结 论

AccessControl的引入是使以太坊生态系统在系统安全方面更接近行业标准的重要一步。

该合约得到了行业专家的大力支持。我想这个合约很快会产生一些有趣而复杂的系统,从而进一步推动。

猜你喜欢

zk rollup(链上数据)与Deversifi的Validium(链下数据)有什么区别?

原文作者:AlexGluchowskiDeversiFi最近推出了由StarkEx交易引擎提供支持的交易所的最新版本。这是一项令人难以置信的技术成就,它提高了用户可以从加密货币

2021-11-29

deVere Group 首席执行官:以太坊崛起为第一大加密货币“似乎不可阻挡”

世界上最大的独立金融咨询公司之一deVereGroup的首席执行官兼创始人NigelGreen表示,以太坊的价格升值将在2021年继续超过比特币。NigelGreen还认为,以

2021-11-29