OpenZeppelin -アクセスコントロール-
OpenZeppelinのドキュメントを読んでみた
Solidityを用いたEthereumのsmart contract開発は、セキュリティが最も大切になります。
一度デプロイしたら、簡単に修正することが難しいという点からも、できる限り既に検証されているコードを使うことが大切です。
本ブログでは、Solidityを用いたsmartcontract開発を行なっているOpenZeppelinのドキュメントを読み、それをまとめてみたいと思います。
アクセスコントロール
コントラクトへのアクセス権限を設定するコードの書き方
Ownable
コントラクトのオーナーのみ関数を実行できるように設定することができます。
onlyOwner modifierを関数に設定することで、その関数はowner以外が呼べなくなります。
また、renounceOwnership関数を呼ぶと、ownerの権限がなくなるため、コントラクトをより非中央集権的にすることができます。ただし、これを行うと、onlyOwnerが使えなくなるため、この点は注意が必要です。
/** * @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 private _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; } /** * @return the address of the owner. */ function owner() public view returns(address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns(bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * @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. * @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; } }
役割ベースのアクセスコントロール
Ownableコントラクトを使った場合、1つの特権アドレスしか設定できません。 アドレスによって、権限を変えたい場合があると思います。 その場合、各ロールをライブラリを使って分けるという方法があります。
/** * @title Roles * @dev Library for managing addresses assigned to a Role. */ library Roles { // 権限を持つアドレスのマッピングを有する構造体 struct Role { mapping (address => bool) bearer; } /** * @dev give an account access to this role */ function add(Role storage role, address account) internal { require(account != address(0)); role.bearer[account] = true; } /** * @dev remove an account's access to this role */ function remove(Role storage role, address account) internal { require(account != address(0)); role.bearer[account] = false; } /** * @dev check if an account has this role * @return bool */ function has(Role storage role, address account) internal view returns (bool) { require(account != address(0)); return role.bearer[account]; } } contract MyToken is DetailedERC20, StandardToken { // RolesライブラリをRoles.Roleに用いる using Roles for Roles.Role; // 各権限をもつ配列を設定する Role private minters; Role private namers; constructor( string name, string symbol, uint8 decimals, address[] minters, address[] namers, ) DetailedERC20(name, symbol, decimals) Standardtoken() public { namers.addMany(namers); minters.addMany(minters); } function mint(address to, uint256 amount) public { // only allow minters to mint require(minters.has(msg.sender), "DOES_NOT_HAVE_MINTER_ROLE"); _mint(to, amount); } function rename(string name, string symbol) public { // only allow namers to name require(namers.has(msg.sender), "DOES_NOT_HAVE_NAMER_ROLE"); name = name; symbol = symbol; } }
まとめ
コントラクトは、アクセス制限を上手く設定することが大切です。
一般的にはOwnableコントラクトが用いられていますが、より複雑なアプリケーションにはRolesコントラクトのように、実行する関数によってアドレスを分けるようにすると、責任の範囲が限定されてよりセキュアなアプリケーションを開発できるようになると思います。