區塊鏈研究實驗室|如何使用OpenZeppelin的新AccessControl合約

買賣虛擬貨幣

原文作者: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的引入是使以太坊生態系統在系統安全方面更接近行業標準的重要一步。

該合約得到了行業專家的大力支援。我想這個合約很快會產生一些有趣而複雜的系統,從而進一步推動。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;