openzeppelin-solidity/contracts的代码学习——access
https://github.com/OpenZeppelin/openzeppelin-solidity/tree/master/contracts/access
access - Smart contracts that enable functionality that can be used for selective restrictions and basic authorization control functions. Includes address whitelisting and signature-based permissions management.
- rbac - A library used to manage addresses assigned to different user roles and an example Role-Based Access Control (RBAC) interface that demonstrates how to handle setters and getters for roles and addresses.
代码解释:
Roles.sol
这里只是底层地实现说明某个地址是否有某个角色,关于角色的详细信息,比如角色的名字,则通过调用该库来实现,如例子RBAC.sol
pragma solidity ^0.4.24; /** * @title Roles * @author Francisco Giordano (@frangio) * @dev Library for managing addresses assigned to a Role.(给一个地址分配一个角色) * See RBAC.sol for example usage.(RBAC.sol是这个库的使用例子) */ library Roles { struct Role { mapping (address => bool) bearer; } /** * @dev give an account access to this role */
//声明为storage是因为形参味memory,声明原因是:一般调用为storage形参 _role= storage传入,这样改变_role的值才会值传递,使storage的传入值也改变
//⚠️形参声明为storage的函数要定义为internal或private function add(Role storage _role, address _account) internal { _role.bearer[_account] = true; } /** * @dev remove an account's access to this role */ function remove(Role storage _role, address _account) internal { _role.bearer[_account] = false; } /** * @dev check if an account has this role * // reverts */ function check(Role storage _role, address _account) internal view { require(has(_role, _account)); } /** * @dev check if an account has this role * @return bool */ function has(Role storage _role, address _account) internal view returns (bool) { return _role.bearer[_account]; } }
RBAC.sol(是使用Roles库的例子)
pragma solidity ^0.4.24; import "./Roles.sol"; /** * @title RBAC (Role-Based Access Control) * @author Matt Condon (@Shrugs) * @dev Stores and provides setters and getters for roles and addresses. * Supports unlimited numbers of roles and addresses. * See //contracts/mocks/RBACMock.sol for an example of usage. * This RBAC method uses strings to key roles. It may be beneficial * for you to write your own implementation of this interface using Enums or similar. */ contract RBAC { using Roles for Roles.Role; mapping (string => Roles.Role) private roles; event RoleAdded(address indexed operator, string role); event RoleRemoved(address indexed operator, string role); /** * @dev reverts if addr does not have role * @param _operator address * @param _role the name of the role,在这里为角色赋名 * // reverts */ function checkRole(address _operator, string _role) public view { roles[_role].check(_operator); } /** * @dev determine if addr has role * @param _operator address * @param _role the name of the role * @return bool */ function hasRole(address _operator, string _role) public view returns (bool) { return roles[_role].has(_operator); } /** * @dev add a role to an address * @param _operator address * @param _role the name of the role */ function _addRole(address _operator, string _role) internal { roles[_role].add(_operator);//所以roles[_role].bearer[_operator]=true emit RoleAdded(_operator, _role); } /** * @dev remove a role from an address * @param _operator address * @param _role the name of the role */ function _removeRole(address _operator, string _role) internal { roles[_role].remove(_operator); emit RoleRemoved(_operator, _role); } /** * @dev modifier to scope access to a single role (uses msg.sender as addr) * @param _role the name of the role * // reverts */ modifier onlyRole(string _role) { checkRole(msg.sender, _role); _; } /** * @dev modifier to scope access to a set of roles (uses msg.sender as addr) * @param _roles the names of the roles to scope access to * // reverts * * @TODO - when solidity supports dynamic arrays as arguments to modifiers, provide this * see: https://github.com/ethereum/solidity/issues/2467 */ // modifier onlyRoles(string[] _roles) { // bool hasAnyRole = false; // for (uint8 i = 0; i < _roles.length; i++) { // if (hasRole(msg.sender, _roles[i])) { // hasAnyRole = true; // break; // } // } // require(hasAnyRole); // _; // } }
contracts/examples/RBACWithAdmin.sol
就是加入了一个admin的角色,这个角色只能一个人拥有。该角色的权利很大,所以一定要根据下面的标准来写,以免出错
pragma solidity ^0.4.24; import "../access/rbac/RBAC.sol"; /** * @title RBACWithAdmin * @author Matt Condon (@Shrugs) * @dev It's recommended that you define constants in the contract, * like ROLE_ADMIN below, to avoid typos. * @notice RBACWithAdmin is probably too expansive and powerful for your * application; an admin is actually able to change any address to any role * which is a very large API surface. It's recommended that you follow a strategy * of strictly defining the abilities of your roles * and the API-surface of your contract. * This is just an example for example's sake. */ contract RBACWithAdmin is RBAC { /** * A constant role name for indicating admins. */ string private constant ROLE_ADMIN = "admin"; /** * @dev modifier to scope access to admins * // reverts */ modifier onlyAdmin() { checkRole(msg.sender, ROLE_ADMIN); _; } /** * @dev constructor. Sets msg.sender as admin by default */ constructor() public { _addRole(msg.sender, ROLE_ADMIN); } /** * @return true if the account is admin, false otherwise. */ function isAdmin(address _account) public view returns(bool) { return hasRole(_account, ROLE_ADMIN); } /** * @dev add a role to an account * @param _account the account that will have the role * @param _roleName the name of the role */ function adminAddRole(address _account, string _roleName) public onlyAdmin { _addRole(_account, _roleName); } /** * @dev remove a role from an account * @param _account the account that will no longer have the role * @param _roleName the name of the role */ function adminRemoveRole(address _account, string _roleName) public onlyAdmin { _removeRole(_account, _roleName); } }
就是又加入了一种角色advisor,这个角色可以多个人拥有
pragma solidity ^0.4.24; import "../examples/RBACWithAdmin.sol"; contract RBACMock is RBACWithAdmin { string internal constant ROLE_ADVISOR = "advisor"; modifier onlyAdminOrAdvisor() { require( isAdmin(msg.sender) || hasRole(msg.sender, ROLE_ADVISOR) ); _; } constructor(address[] _advisors) public { _addRole(msg.sender, ROLE_ADVISOR); for (uint256 i = 0; i < _advisors.length; i++) { _addRole(_advisors[i], ROLE_ADVISOR); } } function onlyAdminsCanDoThis() external onlyAdmin view { } function onlyAdvisorsCanDoThis() external onlyRole(ROLE_ADVISOR) view { } function eitherAdminOrAdvisorCanDoThis() external onlyAdminOrAdvisor view { } function nobodyCanDoThis() external onlyRole("unknown") view { } // admins can remove advisor's role function removeAdvisor(address _account) public onlyAdmin { // revert if the user isn't an advisor // (perhaps you want to soft-fail here instead?) checkRole(_account, ROLE_ADVISOR); // remove the advisor's role _removeRole(_account, ROLE_ADVISOR); } }
()
contracts/ownership/Ownable.sol
pragma solidity ^0.4.24; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipRenounced(address indexed previousOwner); event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to relinquish(放弃) control of the contract. 现在的owner可以放弃对合约的控制 * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipRenounced(owner); owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner.owner将控制权转移 * @param _newOwner The address to transfer ownership to. */ function transferOwnership(address _newOwner) public onlyOwner { _transferOwnership(_newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ function _transferOwnership(address _newOwner) internal { require(_newOwner != address(0)); emit OwnershipTransferred(owner, _newOwner); owner = _newOwner; } }
contracts/cryptography/ECDSA.sol
有关的详细内容可以看本博客的椭圆曲线相关内容
pragma solidity ^0.4.24; /** * @title Elliptic curve signature operations 椭圆曲线 * @dev Based on https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d这是一个完整的例子 * TODO Remove this library once solidity supports passing a signature to ecrecover.因为在solidity中ecrecover函数还不能直接输入signature,所以下面要自己将r,s,v分离出来 * See https://github.com/ethereum/solidity/issues/864建议实现能够直接将signature传递给ecrecover函数,而不是还要转成v,r,s */ library ECDSA { /** * @dev Recover signer address from a message by using their signature * @param _hash bytes32 message, the hash is the signed message. What is recovered is the signer address. * @param _signature bytes signature, the signature is generated using web3.eth.sign() */ function recover(bytes32 _hash, bytes _signature)//将signature的r,v,s分离出来,传到ecrecover函数中 internal pure returns (address) { bytes32 r; bytes32 s; uint8 v; // Check the signature length if (_signature.length != 65) {//签名有65bits return (address(0)); } // Divide the signature in r, s and v variables // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. // solium-disable-next-line security/no-inline-assembly assembly { r := mload(add(_signature, 32))//这是因为前32位存储了_signature变量的长度信息 s := mload(add(_signature, 64)) v := byte(0, mload(add(_signature, 96)))//v代表的是版本,有27和28两种,byte
默认表示byte1
,即1byte } // Version of signature should be 27 or 28, but 0 and 1 are also possible versions if (v < 27) { v += 27; } // If the version is correct return the signer address if (v != 27 && v != 28) { return (address(0)); } else { // solium-disable-next-line arg-overflow return ecrecover(_hash, v, r, s); } } /** * toEthSignedMessageHash * @dev prefix a bytes32 value with "\x19Ethereum Signed Message:" * and hash the result */ function toEthSignedMessageHash(bytes32 _hash) internal pure returns (bytes32) { // 32 is the length in bytes of hash, // enforced by the type signature above return keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash) ); } }
contracts/access/SignatureBouncer.sol
这里的角色是bouncer,保镖
pragma solidity ^0.4.24; import "../ownership/Ownable.sol"; import "../access/rbac/RBAC.sol"; import "../cryptography/ECDSA.sol"; /** * @title SignatureBouncer * @author PhABC, Shrugs and aflesher * @dev Bouncer allows users to submit a signature as a permission to do an action.使用者通过签名来允许进行某行为 * If the signature is from one of the authorized bouncer addresses, the signature如果是一个被授权的bouncer地址进行了签名,该签名也是有效的 * is valid. The owner of the contract adds/removes bouncers. * Bouncer addresses can be individual servers signing grants or different (bouncer地址可能是一个签署授权的单独服务器 * users within a decentralized club that have permission to invite other members. 或者是在一个去中心化团体中且有着权利邀请其他成员的不同使用者 * This technique is useful for whitelists and airdrops; instead of putting all * valid addresses on-chain, simply sign a grant of the form 通过对一个有效的bouncer地址签署授权而不是把所有有效的地址都放到链上 * keccak256(abi.encodePacked(`:contractAddress` + `:granteeAddress`)) using a valid bouncer address. * Then restrict access to your crowdsale/whitelist/airdrop using the //这样你后面就可以通过修饰器来限制一些操作了 * `onlyValidSignature` modifier (or implement your own using _isValidSignature). * In addition to `onlyValidSignature`, `onlyValidSignatureAndMethod` and * `onlyValidSignatureAndData` can be used to restrict access to only a given method * or a given method with given parameters respectively. * See the tests Bouncer.test.js for specific usage examples. * @notice A method that uses the `onlyValidSignatureAndData` modifier must make the _signature * parameter the "last" parameter. You cannot sign a message that has its own * signature in it so the last 128 bytes of msg.data (which represents the 这后面的128 bytes就是签名,当一个信息已经有了自己的签名,这后面再进行的签名将会被忽略 * length of the _signature data and the _signaature data itself) is ignored when validating. * Also non fixed sized parameters make constructing the data in the signature * much more complex. See https://ethereum.stackexchange.com/a/50616 for more details. */ contract SignatureBouncer is Ownable, RBAC { using ECDSA for bytes32; // Name of the bouncer role. string private constant ROLE_BOUNCER = "bouncer"; // Function selectors are 4 bytes long, as documented in // https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector uint256 private constant METHOD_ID_SIZE = 4; // Signature size is 65 bytes (tightly packed v + r + s), but gets padded to 96 bytes uint256 private constant SIGNATURE_SIZE = 96; /** * @dev requires that a valid signature of a bouncer was provided */ modifier onlyValidSignature(bytes _signature) { require(_isValidSignature(msg.sender, _signature)); _; } /** * @dev requires that a valid signature with a specifed method of a bouncer was provided */ modifier onlyValidSignatureAndMethod(bytes _signature) { require(_isValidSignatureAndMethod(msg.sender, _signature)); _; } /** * @dev requires that a valid signature with a specifed method and params of a bouncer was provided */ modifier onlyValidSignatureAndData(bytes _signature) { require(_isValidSignatureAndData(msg.sender, _signature)); _; } /** * @dev Determine if an account has the bouncer role. * @return true if the account is a bouncer, false otherwise. */ function isBouncer(address _account) public view returns(bool) { return hasRole(_account, ROLE_BOUNCER); } /** * @dev allows the owner to add additional bouncer addresses */ function addBouncer(address _bouncer) public onlyOwner { require(_bouncer != address(0)); _addRole(_bouncer, ROLE_BOUNCER); } /** * @dev allows the owner to remove bouncer addresses */ function removeBouncer(address _bouncer) public onlyOwner { _removeRole(_bouncer, ROLE_BOUNCER); } /** * @dev is the signature of `this + sender` from a bouncer? * @return bool */ function _isValidSignature(address _address, bytes _signature) internal view returns (bool) { return _isValidDataHash( keccak256(abi.encodePacked(address(this), _address)),//将所给的参数打包再一起进行ABI编码,然后进行hash _signature //该_signature是对`this + sender`内容的签名,所以上面的hash是那样的 ); } /** * @dev is the signature of `this + sender + methodId` from a bouncer? * @return bool */ function _isValidSignatureAndMethod(address _address, bytes _signature) internal view returns (bool) { bytes memory data = new bytes(METHOD_ID_SIZE);//4个字节bytes for (uint i = 0; i < data.length; i++) { data[i] = msg.data[i]; } return _isValidDataHash( keccak256(abi.encodePacked(address(this), _address, data)), _signature ); } /** * @dev is the signature of `this + sender + methodId + params(s)` from a bouncer? * @notice the _signature parameter of the method being validated must be the "last" parameter * @return bool */ function _isValidSignatureAndData(address _address, bytes _signature) internal view returns (bool) { require(msg.data.length > SIGNATURE_SIZE); bytes memory data = new bytes(msg.data.length - SIGNATURE_SIZE);//msg.data中除掉签名的内容96bytes就是数据data,签名的内容放在msg.data的最后面 for (uint i = 0; i < data.length; i++) { data[i] = msg.data[i]; } return _isValidDataHash( keccak256(abi.encodePacked(address(this), _address, data)), _signature ); } /** * @dev internal function to convert a hash to an eth signed message * and then recover the signature and check it against the bouncer role * @return bool */ function _isValidDataHash(bytes32 _hash, bytes _signature) internal view returns (bool) { address signer = _hash //这两个都是ECDSA.sol里的函数 .toEthSignedMessageHash()//convert a hash to an eth signed message .recover(_signature);//调用库的recover函数,得到签名的公钥,即地址signer return isBouncer(signer); } }
contracts/access/Whitelist.sol
声明白名单,即在这个名单中的address是可信赖的,可进行一些操作
pragma solidity ^0.4.24; import "../ownership/Ownable.sol"; import "../access/rbac/RBAC.sol"; /** * @title Whitelist * @dev The Whitelist contract has a whitelist of addresses, and provides basic authorization control functions. * This simplifies the implementation of "user permissions". */ contract Whitelist is Ownable, RBAC { // Name of the whitelisted role. string private constant ROLE_WHITELISTED = "whitelist"; /** * @dev Throws if operator is not whitelisted. * @param _operator address */ modifier onlyIfWhitelisted(address _operator) { checkRole(_operator, ROLE_WHITELISTED); _; } /** * @dev add an address to the whitelist * @param _operator address * @return true if the address was added to the whitelist, false if the address was already in the whitelist */ function addAddressToWhitelist(address _operator) public onlyOwner { _addRole(_operator, ROLE_WHITELISTED); } /** * @dev Determine if an account is whitelisted. * @return true if the account is whitelisted, false otherwise. */ function isWhitelisted(address _operator) public view returns (bool) { return hasRole(_operator, ROLE_WHITELISTED); } /** * @dev add addresses to the whitelist * @param _operators addresses * @return true if at least one address was added to the whitelist, * false if all addresses were already in the whitelist */ function addAddressesToWhitelist(address[] _operators) public onlyOwner { for (uint256 i = 0; i < _operators.length; i++) { addAddressToWhitelist(_operators[i]); } } /** * @dev remove an address from the whitelist * @param _operator address * @return true if the address was removed from the whitelist, * false if the address wasn't in the whitelist in the first place */ function removeAddressFromWhitelist(address _operator) public onlyOwner { _removeRole(_operator, ROLE_WHITELISTED); } /** * @dev remove addresses from the whitelist * @param _operators addresses * @return true if at least one address was removed from the whitelist, * false if all addresses weren't in the whitelist in the first place */ function removeAddressesFromWhitelist(address[] _operators) public onlyOwner { for (uint256 i = 0; i < _operators.length; i++) { removeAddressFromWhitelist(_operators[i]); } } }