LOADING STUFF...

脑洞大开?用区块链技术编写一个安全的遗嘱继承智能合约

用区块链技术编写一个安全的遗嘱继承智能合约

合约编写开发联系:Dexdao123

我已经准备好面对死亡了,只是还没有准备好面对我的账户余额.

随着人口老龄化的加剧,如何规划遗产成为了越来越多人需要面对的问题。而传统的遗嘱继承方式存在诸多问题,例如需要经过繁琐的法律程序、易受篡改等。而区块链技术的出现为遗嘱继承带来了全新的解决方案,通过智能合约实现自动化遗嘱继承,不仅能够减少法律程序的复杂性,同时还能够保证遗嘱继承的安全性和不可篡改性。

本文将会介绍如何利用区块链技术编写一个安全的遗嘱继承智能合约,用于在你死后将你的以太转移到指定受益人账户。合约的设计思路如下:

  1. 在生前使用智能合约来保存你的以太,随时可存取;
  2. 智能合约内置有探测你是否死亡的机制;
  3. 受益人能在你死后成为合约的主人。

此外,该合约的应用场景还包括:

  • Alice设立了自己的遗嘱合约,可以往里面存以太,也可以取出来。Alice在设立之初指定了合约继承人和等待时间(waitingPeriodLength),还调用了心跳修饰符,将等待结束时间点(endOfWaitingPeriod)设置为无限远(宇宙的生命周期),等待结束时间之前,受益方都不能拿走合约的权限;
  • 存款函数和取款函数都会调用心跳修饰符,相当于是合约所有者声明:我还活着,受益方不能拿走我的合约权限;
  • 但是受益方不知道合约所有者死亡与否,只能通过assertDeath函数来探查。受益方通过调用assertDeath函数,将等待结束时间设置为当前时间的等待时间(waitingPeriodLength)秒后,在这期间如果合约所有人没有和合约进行任何交互,将会被判定为死亡,受益方就能拿走合约权限了。

接下来将会详细介绍该合约的具体实现过程。

照例,先上代码和注释,如果能看懂,就没必要看后面的说明了。

pragma solidity ^0.8.0;

contract Ownable {
address immutable owner;//把合约部署者设定为合约所有者
constructor() {
    owner = msg.sender;
}

modifier onlyOwner {//此修饰符出现的函数里要求只有合约所有者才能调用
    require(msg.sender == owner, "Only the contract owner can perform this action.");
    _;
}
}

contract Mortal is Ownable {//继承Ownable合约
function destroy() public onlyOwner {//调用onlyOwner修饰符
selfdestruct(payable(owner));//调用selfdestruct自毁函数,毁灭合约,自毁函数会把余额转给调用者
}
}

contract Estate is Mortal {//继承Mortal合约
address immutable beneficiary;//设置受益方变量
uint256 immutable waitingPeriodLength;//变量,等待时长
uint256 endOfWaitingPeriod;//变量,等待结束时间点
constructor(address _beneficiary, uint256 _waitingPeriodLength) {
    beneficiary = _beneficiary;
    waitingPeriodLength = _waitingPeriodLength;
    endOfWaitingPeriod = block.timestamp + _waitingPeriodLength;
}

modifier onlyBeneficiary {//此修饰符出现的函数里要求受益方才能调用
    require(msg.sender == beneficiary, "Only the beneficiary can perform this action.");
    _;
}

//合约所有者每一次调用函数,都会有heartbeat修饰符,也就是把合约里自己的死亡时间延长到无限远
//而当受益方调用assertDeath函数时,会把合约所有者的等待结束时间点设为now+等待时长,如果在此时间段内合约所有者不进行任何交互,就会被判定死亡
//所以这时候合约所有者设置的等待时长就很关键了,如果合约所有者故意设置为无限远,比如10**18,那么受益方一辈子都不可能继承了
modifier heartbeat {//此修饰符出现的函数里会在最后一行调用此修饰符
    _;
    endOfWaitingPeriod = block.timestamp + waitingPeriodLength;  //将等待结束时间点设置为当前时间+等待时长
}

function deposit() public payable onlyOwner heartbeat { }

function withdraw(uint256 amount) public onlyOwner heartbeat {
    require(amount <= address(this).balance, "Insufficient balance.");
    payable(msg.sender).transfer(amount);
}

event Challenge(uint256 timestamp, uint256 endOfWaitingPeriod);

//看看死了没 函数,
function assertDeath() public onlyBeneficiary {
    endOfWaitingPeriod = block.timestamp + waitingPeriodLength;//把等待结束时间点延长到当前时间+等待时长 后
    emit Challenge(block.timestamp, endOfWaitingPeriod);//释放Challenge事件,参数包含当前时间点和等待结束时间点
}

function claimInheritance(address newBeneficiary)
    public
    onlyBeneficiary
    heartbeat//调用heartbeat修饰符,重设等
contract YeDun {
address public owner;
uint256 public totalShares; // 总共的份额
uint256 public totalWithdrawn; // 总共提现的份额
uint256 public totalDeposits; // 总共存入的份额
struct Beneficiary {
    uint256 shares; // 每个受益人的份额
    uint256 withdrawn; // 受益人已经提现的份额
}

mapping(address => Beneficiary) public beneficiaries; // 每个受益人的信息

constructor() public {
    owner = msg.sender; // 合约创建者为owner
}

function deposit() public payable {
    require(msg.value > 0, "Deposit value must be greater than 0"); // 存入金额必须大于0
    totalDeposits += msg.value; // 更新总存入份额
    uint256 sharesPerEther = totalShares / totalDeposits; // 每个以太坊的份额
    uint256 shares = msg.value * sharesPerEther; // 存入金额对应的份额
    beneficiaries[msg.sender].shares += shares; // 更新受益人份额
    totalShares += shares; // 更新总份额
}

function withdraw(uint256 amount) public {
    require(amount > 0, "Withdraw amount must be greater than 0"); // 提现金额必须大于0
    require(amount <= beneficiaries[msg.sender].shares - beneficiaries[msg.sender].withdrawn, "Insufficient shares"); // 受益人份额不足
    beneficiaries[msg.sender].withdrawn += amount; // 更新受益人已提现份额
    totalWithdrawn += amount; // 更新总提现份额
    uint256 etherPerShare = totalDeposits / totalShares; // 每份对应的以太坊
    uint256 etherAmount = amount * etherPerShare; // 提现金额对应的以太坊
    msg.sender.transfer(etherAmount); // 向受益人转账
}

function addBeneficiary(address beneficiary, uint256 shares) public onlyOwner {
    require(shares > 0, "Shares must be greater than 0"); // 分配份额必须大于0
    require(beneficiaries[beneficiary].shares == 0, "Beneficiary already exists"); // 受益人已存在
    beneficiaries[beneficiary].shares = shares; // 分配份额给受益人
    totalShares += shares; // 更新总份额
}

function removeBeneficiary(address beneficiary) public onlyOwner {
    require(beneficiaries[beneficiary].shares > 0, "Beneficiary does not exist"); // 受益人不存在
    totalShares -= beneficiaries[beneficiary].shares; // 减去受益人的份额
    beneficiaries[beneficiary].shares = 0; // 受益人份额清零
}

modifier onlyOwner() {
    require(msg.sender == owner, "Only owner can call this function"); // 只有合约创建者可以调用此函数
    _;
}
}



合约继承(contract inheritance)是Solidity语言的一项特性,它允许一个合约继承另一个合约的函数、修饰符和变量等。类似于其他面向对象编程语言中的继承机制。在Solidity中,继承机制产生了很多常见的模式,其中最常见的两种是用于设置合约的主人和销毁合约。

contract Ownable {
    address owner = msg.sender;
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
}

contract Mortal is Ownable {
    function kill() public onlyOwner {
        selfDestruct(msg.sender);
    }
}

contract Estate is Mortal {
    //...
}

以上代码使用继承来给Estate合约提供了Ownable,Mortal合约的一系列变量、修饰符和函数(owner、onlyOwner、kill等)

 

受益方(beneficiary)

Estate合约里除了有主人账户,还有受益方账户。受益方在合约主人死后继承此合约。对于受益方也有相应的修饰符.

contract Estate is Mortal {
    address beneficiary;
    modifier onlyBeneficiary {
        require(msg.sender == beneficiary);
        _;
    }
    //...
}

怎么知道我死了没?

此合约要解决的挑战之一:怎么知道合约主人死了没。由于这是链下事件,所以很难知晓。与其解决这个难题,我现在宁愿部署一个代理来评估死亡与否——在特定时间段内没有心跳就可以断定为死亡。

具体来说,机制如下:受益方会在一个等待期内查看(assert)合约主人是否还在,合约主人必须提供“心跳(heartbeat)”交易来证明自己还健在。如果合约主人能在等待期结束前“心跳”了,那么就没死;否则合约就会判定合约主人已死亡,并会在受益人要求的时候把所有权移交给受益人。

为了实现以上功能,会使用以下两个变量:

uint256 waitingPeriodLength;
uint256 endOfWaitingPeriod;

说明:

  • waitingPeriodLength是受益方发出询问之后留给合约主人证明自己没死的等待时间;
  • endOfWaitingPeroid:等待时间结束的点。

简单点,我们用一个哨兵(sentinel)变量endOfWaitingPeriod,这个变量代表了受益人还没有询问合约主人死亡情况的时候。

我们定义一个heartbeat修饰符,作用是在等待期末的时候重设endOfWaitingPeriod的值。

modifier heartbeat {
    _;
    endOfWaitingPeriod = 10 ** 18;//约等于宇宙的寿命
}

heartbeat修饰符和其他的修饰符不太一样,会在函数末尾才会检查。

先简单搭个脚手架:

function Estate(address _beneficiary, uint256 _waitingPeriodLenght)
    public
    heartbeat
{
    beneficiary = _beneficiary;
    waittingPeriodLength = _waitingPeriodLength;
}

这个合约直接设置了beneficiary和waitingPeriodLength,并且还通过heartbeat修饰符设置了endOfWaitingPeriod变量。

断言死亡

受益方可以用assertDeath函数来判断合约主人是否死亡:

event Challenge(uint256 endOfWaitingPeriod);
function assertDeath() public onlyBeneficiary {
    endOfWaitingPeriod = now + waitingPeriodLength;
    emit Challenge(now);
}

说明:

assertDeath把endOfWaitingPeriod设置在waitingPeriodLength秒后。如果合约主人没和合约交互、用heartbeat修饰符重置endOfWaitingPeriod,那么受益方就可以获得合约所有权了。

获取所有权

受益人可以通过claimInHeritance函数获取所有权并设置自己的受益人:

function claimInHeritance(address newBeneficiary)
    public
    onlyBeneficiary
    heartbeat
{
    require(now > endOfWaitingPeriod);
    owner = beneficiary;
    beneficairy = newBeneficiary;
}

要注意heartbeat这个修饰符,这是为了给合约的新主人设置断言的截止日期。

 

处理以太

合约主人可以处理自己的以太余额:

function deposit() public payable onlyOwner heartbeat {
    //什么都不用做
}

function withdraw(uint256 amount) public onlyOwner heartbeat {
    msg.sender.transfer(amount);
}

说明:

  • 取款的时候之所以没用require检查,是因为transfer本身就会检查(反而省gas?)
  • deposit函数和withdraw函数都使用了onlyOwner和heartbeat修饰符
  • 当合约主人存入0个以太也会出发heartbeat修饰符

总结:本文介绍了如何利用区块链技术编写一个安全的遗嘱继承智能合约,其设计思路包括在生前使用智能合约来保存以太、内置探测死亡机制以及使受益人能在死后成为合约的主人。该合约的应用场景包括在设立之初指定合约继承人和等待时间,存取以太时调用心跳修饰符声明合约所有者还活着,受益方通过调用assertDeath函数探查合约所有者是否死亡并在等待时间结束后拿走合约权限。

我相信死亡并不是最终的归宿,但如果是的话,那我希望能死在自己的床上,而不是在工作上.

© 版权声明

相关文章

暂无评论

暂无评论...