暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

OpenZeppelin 2.0 基础合约详解二

烯分数据 2019-01-28
1359

归属权 (ownership)

  • 归属 (Ownable.sol) - contract

注意renouceOwnership
这个函数

Ownable
是一个我们使用得非常多的一个合约,相当于确定了一个合约的Owner。

1event OwnershipTransferred(
2    address indexed previousOwner, 
3    address indexed newOwner
4);

OwnershipTransferred
是一个event
,表示这个合约的所属权改变。两个参数previousOwner
newOwner
都声明为indexed
,所以我们在JSONRPC
web3
接口里可以用filter
去筛选指定事件。比如previousOwner
是某一个地址的所有的事件。

1/**
2 * @dev The Ownable constructor sets the original `owner` of the contract to the sender
3 * account.
4 */

5constructor () internal {
6    _owner = msg.sender;
7    emit OwnershipTransferred(address(0), _owner);
8}

constructor
是一个internal
的函数,将msg.sender
设置成这个合约的Owner,即发送合约创建交易的EOA
或者new
了这个子合约的合约地址。然后生成一个OwnershipTransferred
事件,表示我们合约owner从0
地址变成了现在的owner。

 1/**
2 * @dev Allows the current owner to relinquish control of the contract.
3 * @notice Renouncing to ownership will leave the contract without an owner.
4 * It will not be possible to call the functions with the `onlyOwner`
5 * modifier anymore.
6 */

7function renounceOwnership() public onlyOwner {
8    emit OwnershipTransferred(_owner, address(0));
9    _owner = address(0);
10}

renounceOwnership
函数用于放弃合约归属权,将合约owner改成0
地址。一旦调用了这个函数,那么不可能再会有地址成为这个合约的owner,因为这个合约的owner已经变成0
地址,而0
地址是不是通过私钥能控制的。

我们为什么需要这么一个函数?

假如我们这个合约是一个需要长时间运行的合约。在合约创建时这个合约需要一个管理员设定一些具体的参数,但这些修改只能由合约owner做。一旦参数修改到大家达成一致之后,参数就不能再修改了,那么我们就应该放弃合约owner,这样就没有人能去修改这个参数,这样就可以防止一系列问题。

  • 从属 (Secondary.sol) - contract

注意这两个基础合约仅有的区别

Secondary
几乎与Ownable
一样,但比Ownable
要简单.Secondary
没有renounceOwnership
,即Secondary
合约必须要有owner。

假如某一个合约不能单独存在的话,就需要使用Secondary
,说明这个合约附属于一个owner,或者附属于一个合约。

访问控制 (access)

  • 角色 (Roles.sol) - library

    library中的internal
    类型的函数,必须使用using for
    的方式来调用。

 1/**
2 * @dev give an account access to this role
3 */

4function add(Role storage role, address accountinternal {
5    require(account != address(0));
6    require(!has(role, account));
7
8    role.bearer[account] = true;
9}
10
11/**
12 * @dev remove an account's access to this role
13 */

14function remove(Role storage role, address accountinternal {
15    require(account != address(0));
16    require(has(role, account));
17
18    role.bearer[account] = false;
19}
20
21/**
22 * @dev check if an account has this role
23 * @return bool
24 */

25function has(Role storage role, address accountinternal view returns (bool{
26    require(account != address(0));
27    return role.bearer[account];
28}

add
reomve
has
函数非常简单,第一个参数都是Role storage role
。所以应该使用using Roles for Roles.Role

  • CapperRole - contract

  • MinterRole - contract

  • PauserRole - contract

  • SignerRole - contract

四个有特殊业务意义的角色模板合约,只是名字不同,代码逻辑是一样的。

 1contract CapperRole {
2    using Roles for Roles.Role;
3
4    event CapperAdded(address indexed account);
5    event CapperRemoved(address indexed account);
6
7    Roles.Role private _cappers;
8
9    constructor () internal {
10        _addCapper(msg.sender);
11    }
12
13    modifier onlyCapper() {
14        require(isCapper(msg.sender));
15        _;
16    }
17
18    function isCapper(address accountpublic view returns (bool{
19        return _cappers.has(account);
20    }
21
22    function addCapper(address accountpublic onlyCapper {
23        _addCapper(account);
24    }
25
26    function renounceCapper() public {
27        _removeCapper(msg.sender);
28    }
29
30    function _addCapper(address accountinternal {
31        _cappers.add(account);
32        emit CapperAdded(account);
33    }
34
35    function _removeCapper(address accountinternal {
36        _cappers.remove(account);
37        emit CapperRemoved(account);
38    }
39}

注意:所有添加到capper中的地址都能添加其他地址到capper。

生命周期 (lifecycle)

  • 可暂停运作 (Pausable.sol) - contract

合约代码非常简单。Pausable
合约继承了PauserRole
,即该合约的创建者可以使用pause
unpause
函数。

1/**
2 * @title Pausable
3 * @dev Base contract which allows children to implement an emergency stop mechanism.
4 */

5contract Pausable is PauserRole

使用这个合约是因为我们的合约一旦发布,便不可修改。若发现该我们的合约有bug,便需要一定的机制暂停使用该合约,防止一些用户利用此bug做一些羞羞的事情。

支付 (payment)

  • 分割付款 (PaymentSplitter.sol) - contract

状态变量:

1uint256 private _totalShares;   //总股份数
2uint256 private _totalReleased; //已经取出的ether总数
3
4mapping(address => uint256) private _shares;    //每个地址持有的股份数
5mapping(address => uint256) private _released;  //每个地址已经取出的ether数
6address[] private _payees;  //能从合约取款的地址

一个payable
fallback
函数,所有人都能向这个合约支付ether。

1/**
2 * @dev payable fallback
3 */

4function () external payable {
5    emit PaymentReceived(msg.sender, msg.value);
6}

PaymentSplitter
合约使用场景?

假如有三个人共同开了一个网店,网店的所有收入通过PaymentSplitter
合约保存。其他人可在这个网店Dapp的前端付款,将ether转到这个合约里。开网店的三个人在合约里都有一定的股份持有数,基于这个股份数,可计算出每个人可以从这个合约里取出多少ether。

release
函数可在任意时刻由任意账户调用,函数逻辑如下:

 1/**
2 * @dev Release one of the payee's proportional payment.
3 * @param account Whose payments will be released.
4 */

5function release(address payable accountpublic {
6    require(_shares[account] > 0);
7
8    uint256 totalReceived = address(this).balance.add(_totalReleased);  //计算此合约历史总收入
9
10    //当前账户可提取的ether = 总输入 * 股份所占比例 - 当前账户已经提取的ether
11    uint256 payment = totalReceived.mul(_shares[account]).div(_totalShares).sub(_released[account]);    
12
13    require(payment != 0);
14
15    _released[account] = _released[account].add(payment);   //修改当前账户已经提取的ether
16    _totalReleased = _totalReleased.add(payment);   //修改总ether提取数
17
18    account.transfer(payment);  //转账
19    emit PaymentReleased(account, payment);
20}

release
函数可以反复调用,若某个账户调用了该函数取走他应得的ether之后,在没有新收入的前提下,这个合约上面剩余的ether全部属于其他账户,这种情况下该账户能取得ether数为0;当某个账户取走ether之后,若有新的收入转入到这个合约中,应该从新的收入中计算这个账户可取出的ether,因为旧的收入全部属于其他账户。

release
函数通过计算某个账户在当前合约余额下可提取的ether的总数减去已经提取的数量,得到该账户还能提取的数量。

release
函数用于向某一个地址支付该地址应该持有的数额。

注意理解这个合约的数据设计和release
函数的逻辑。(为什么不在每次账户余额变动的时候去计算每个payee的可提取额度,并单独保存他们的可提取额度?)

理解这个合约的实践意义 (比如可以用DAO 或者股东分红等场景)。

  • 托管 (Escrow.sol) - contract

 1 /**
2 * @title Escrow
3 * @dev Base escrow contract, holds funds designated for a payee until they
4 * withdraw them.
5 * @dev Intended usage: This contract (and derived escrow contracts) should be a
6 * standalone contract, that only interacts with the contract that instantiated
7 * it. That way, it is guaranteed that all Ether will be handled according to
8 * the Escrow rules, and there is no need to check for payable functions or
9 * transfers in the inheritance tree. The contract that uses the escrow as its
10 * payment method should be its primary, and provide public methods redirecting
11 * to the escrow's deposit and withdraw.
12 */

13contract Escrow is Secondary

Escrow
合约继承自Secondary
合约,相当于给这个合约设置了一个owner,且合约owner不能被renounceOwnership

 1/**
2* @dev Stores the sent amount as credit to be withdrawn.
3* @param payee The destination address of the funds.
4*/

5function deposit(address payeepublic onlyPrimary payable {
6    uint256 amount = msg.value;
7    _deposits[payee] = _deposits[payee].add(amount);
8
9    emit Deposited(payee, amount);
10}

deposit
函数用于由primary(管理这个合约的人)
向这个托管合约打一笔ether,并指定这笔ether应该支付给谁(payee
)。

 1/**
2* @dev Withdraw accumulated balance for a payee.
3* @param payee The address whose funds will be withdrawn and transferred to.
4*/

5function withdraw(address payable payeepublic onlyPrimary {
6    uint256 payment = _deposits[payee];
7
8    _deposits[payee] = 0;
9
10    payee.transfer(payment);
11
12    emit Withdrawn(payee, payment);
13}

withdraw
函数只能由primary(管理这个合约的人)
调用,指定给哪个账户支付ether。

合约继承自 Secondary
,而不是Ownable

  • 条件托管 (ConditionalEscrow.sol) - contract

 1/**
2 * @title ConditionalEscrow
3 * @dev Base abstract escrow to only allow withdrawal if a condition is met.
4 * @dev Intended usage: See Escrow.sol. Same usage guidelines apply here.
5 */

6contract ConditionalEscrow is Escrow {
7    /**
8    * @dev Returns whether an address is allowed to withdraw their funds. To be
9    * implemented by derived contracts.
10    * @param payee The destination address of the funds.
11    */

12    function withdrawalAllowed(address payeepublic view returns (bool);
13
14    function withdraw(address payable payeepublic 
{
15        require(withdrawalAllowed(payee));
16        super.withdraw(payee);
17    }
18}

ConditionalEscrow
合约继承自Escrow
,定义了一个抽象函数withdrawalAllowed
,这个函数用于返回所传入的账户是否能够提取ether。并且这个合约重写了withdraw
函数,先判断了所传入的账户能提取ether之后,再使用Escrow
withdraw
函数的代码进行转账。

  • 偿还托管 (RefundEscrow.sol) - contract

 1/**
2 * @title RefundEscrow
3 * @dev Escrow that holds funds for a beneficiary, deposited from multiple
4 * parties.
5 * @dev Intended usage: See Escrow.sol. Same usage guidelines apply here.
6 * @dev The primary account (that is, the contract that instantiates this
7 * contract) may deposit, close the deposit period, and allow for either
8 * withdrawal by the beneficiary, or refunds to the depositors. All interactions
9 * with RefundEscrow will be made through the primary contract. See the
10 * RefundableCrowdsale contract for an example of RefundEscrow’s use.
11 */

12contract RefundEscrow is ConditionalEscrow

RefundEscrow
合约继承自ConditionalEscrow

1enum State { Active, Refunding, Closed }    //状态枚举
2
3event RefundsClosed();
4event RefundsEnabled();
5
6State private _state;   //合约状态
7address payable private _beneficiary; //受益人

RefundEscrow
合约重写了deposit
函数。

1/**
2 * @dev Stores funds that may later be refunded.
3 * @param refundee The address funds will be sent to if a refund occurs.
4 */

5function deposit(address refundeepublic payable {
6    require(_state == State.Active);
7    super.deposit(refundee);
8}

只有在合约状态是Active
才能进行充值。

1/**
2 * @dev Withdraws the beneficiary's funds.
3 */

4function beneficiaryWithdraw() public {
5    require(_state == State.Closed);
6    _beneficiary.transfer(address(this).balance);
7}

beneficiaryWithdraw
只能在合约状态为Close
时才能调用,将账户里的余额转给受益人_beneficiary

1/**
2 * @dev Returns whether refundees can withdraw their deposits (be refunded). The overriden function receives a
3 * 'payee' argument, but we ignore it here since the condition is global, not per-payee.
4 */

5function withdrawalAllowed(addresspublic view returns (bool{
6    return _state == State.Refunding;
7}

withdrawalAllowed
函数实现了ConditionalEscrow
合约中的抽象函数。

注意这个合约重写了deposit
函数;用了一个enum
来控制合约状态;实现了ConditionalEscrow
withdrawAllowed
函数。

  • 需收款人主动提取的付款 (PullPayment.sol) - contract

这是一个安全的付款模式(也就是withdraw
模式),有很强的实践意义。

1Escrow private _escrow;

PullPayment
合约定义了一个private
的状态变量_escrow

1constructor () internal {
2    _escrow = new Escrow();
3}

构造函数constructor
中使用了一个new
,生成一个Escrow
的合约实例,并将这个合约实例保存到状态变量_escrow
中。

注意constructor
internal
类型,所以无法直接创建该合约实例,只能继承该合约。

1/**
2* @dev Called by the payer to store the sent amount as credit to be pulled.
3* @param dest The destination address of the funds.
4* @param amount The amount to transfer.
5*/

6function _asyncTransfer(address dest, uint256 amountinternal {
7    _escrow.deposit.value(amount)(dest);
8}

_asyncTransfer
函数是一个internal
类型,不能外部直接调用。我们可以在子合约中通过逻辑包装,当满足某一个特定的业务逻辑的时候,我们可以调用_asyncTransfer
对某一个地址进行付款。

目前OpenZeppelin 2.0
ERC20
ERC721
无关的通用的基础合约已经全部介绍完毕,这些合约具有很高的复用价值。这些合约的设计和代码的书写很值得我们学习。


文章转载自烯分数据,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论