水龙头合约

前言

水龙头是什么

水龙头这个名字总会让我想起生活中把水龙头开关拧到无限接近但又不达到关闭状态的大妈们,这样可以让水一滴一滴缓慢滴出,但又不会触发水表计费,彰显出她们丰富的生活经验和生存智慧。

水龙头是赠送小额比特币的服务。
为了让用户可以快速尝试Bitcoin SV网络,会有人搭建水龙头服务,给用户赠送给小额比特币,这样用户就可以用这些币尝试使用Bitcoin SV网络,如:转账、测试、写入数据等。
——wiki.bsv.info

合约需求

本文介绍如何通过智能合约直接在链上提供水龙头服务。该服务满足如下需求:

  1. 任何人都可以向合约中充值。
  2. 每隔一段时间,任何人都可以从合约中提取一定额度的BSV。

完整的代码已经合入了sCrypt的样板项目中。

准备知识

阅读本文前需要先了解OP_PUSH_TX的相关知识,推荐阅读如下文章:

代码分析

总体结构

该合约共有三个部分:

  • 充值合约 FaucetDeposit
  • 提现合约 FaucetWithdraw
  • 水龙头合约 Faucet
    对外可见的是水龙头合约,其他两个合约不可见,只是帮助水龙头合约实现具体功能。顾名思义,充值合约实现充值功能,提现合约实现提现功能。虽然定义了多个合约,但最终编译出来的脚本还是放在一个UTXO中。这种多合约模式的使用方法和特点,可以参见sCrypt的说明文档多合约

水龙头合约对外提供两个函数:

  • 充值deposit
  • 提现drop

充值功能分析

函数参数

  • SigHashPreimage preImage:当前tx的签名哈希原像。如果你不知道这个参数的含义,请阅读文章开始部分推荐的文章。
  • int depositAmount:充值聪数。
  • Ripemd160 changePKH:找零用的公钥Hash。
  • int changeAmount: 找零聪数。

参数检查

require(Tx.checkPreimage(preImage));
require(depositAmount > 0);

对参数进行取值范围的检查。
因为sCrypt目前还不支持unsigned int类型,所以需要检查depositAmount参数是正数,避免出现利用充值函数从合约中取走币的漏洞。

构造合约输出

合约规定充值tx最多会有两个有先后顺序的输出:

  1. 充值后的合约
  2. 找零(可选)
bytes output0 = this.composeOutput0(preImage, depositAmount);
function composeOutput0(SigHashPreimage preImage, int depositAmount):bytes{
    bytes lockingScript = Util.scriptCode(preImage);
    int contractTotalAmount = Util.value(preImage) + depositAmount;
    return Util.buildOutput(lockingScript, contractTotalAmount);
}

充值前合约里的余额加上要充值的额度就是充值后的合约里的余额。这里你就可以理解为什么要检查depositAmount是正数了。
充值不会改变合约的脚本,所以用上一个合约的脚本和充值后的余额就可以拼出充值后的合约输出。

构造找零输出

bytes output1 = this.composeOutput1(changePKH, changeAmount);
function composeOutput1(Ripemd160 changePKH, int changeAmount):bytes{
    bytes output1 = b'';
    if(changeAmount > 546){
        output1 = Util.buildOutput(Util.buildPublicKeyHashScript(changePKH), changeAmount);
    }
    return output1;
}

如果找零额度小于546聪,则认为不需要找零,也就没有找零输出。
根据找零PKH构造出P2PKH格式的输出脚本,再结合找零额度,就可以构造出完整的输出。

校验所有输出的哈希值

Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));

提现功能分析

函数参数

  • SigHashPreimage preImage:当前tx的签名哈希原像。
  • Ripemd160 receiverPKH:接收者的公钥哈希。

参数检查

require(Tx.checkPreimage(preImage));
require(Util.nSequence(preImage) < 0xFFFFFFFF);

为了满足两次提现之间的时间间隔,需要使用比特币的nLocktime功能。设置了nLocktime的值后,能够限制合约在该时间之前被花费,也就阻止了在该时间之前发起下一次提现转账。
要让nLocktime生效,则需要让合约输入的nSequence小于0xFFFFFFFF。

手续费和提现金额

int withdrawAmount = 2000000;
int fee = 3000;

我们简单粗暴地把每次赠送的数额设置为0.02BSV,不能多也不能少。
我们同样简单粗暴地把每次转账的费用设置为3000聪,基本上相当于0.5聪每字节。

构造合约输出

合约规定提现tx最多会有两个有先后顺序的输出:

  1. 合约输出
  2. 赠送输出(可选)
bytes output0 = this.composeOutput0(preImage, withdrawAmount, fee);
function composeOutput0(SigHashPreimage preImage, int withdrawAmount, int fee):bytes{
    bytes prevLockingScript = Util.scriptCode(preImage);
    int scriptLen = len(prevLockingScript);

    int fiveMinutesInSecond = 300;
    int newMatureTime = this.getPrevMatureTime(prevLockingScript, scriptLen) + fiveMinutesInSecond;
    require(Util.nLocktime(preImage) == newMatureTime);

    bytes codePart = this.getCodePart(prevLockingScript, scriptLen);
    bytes script = codePart + pack(newMatureTime);
    int amount = Util.value(preImage) - withdrawAmount - fee;
    return Util.buildOutput(script, amount);
}

合约输出的数据部分是一个四字节的时间戳,也就是matureTime,该值与tx的nLocktime相等,表示合约在该时刻之后才可以被花费(充值或提现)。
合约中记录matureTime的目的是为了记住合约所在的上一个tx的nLocktime,从而可以用该值对当前tx的nLocktime进行校验。
合约设计成每隔5分钟可以被花费一次,那么matureTime的值每次都会增加300秒,保证nLocktime的值也是按照该规律增加,从而最终保证每次花费合约之间的间隔为5分钟。
老合约的余额减去赠送的数额,再减去手续费,就是新合约里的余额。脚本部分和余额部分组合在一起形成新合约的输出。

构造提现输出

bytes output1 = this.composeOutput1(receiverPKH, withdrawAmount);
function composeOutput1(Ripemd160 receiver, int withdrawAmount):bytes{
    bytes script = Util.buildPublicKeyHashScript(receiver);
    return Util.buildOutput(script, withdrawAmount);
}

检查所有输出的哈希值

Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));

总结

测试网络上已经部署了该合约:

感谢晓峰大爷提供测试网络的BSV。

  • 2
    点赞
  • 1
    收藏 更改收藏夹
  • 打赏
    打赏
  • 6
    评论
【技术背景】<br /><span style="color:#333333;">区块链,是一个分布式的共享账本和数据库,具有去中心化、不可篡改、可追溯、公开透明等特点。区块链技术作为科技创新的代表和未来技术的发展方向,已经上升至国家战略高度。它将为解决信息不对称问题、创造信任与合作机制等提供丰富的应用空间,也会是未来我们技术自主创新、引领产业变革的重要突破口。</span><br /><br /><span style="color:#333333;">比特币被认为是区块链技术1.0版的应用,主要实现的是电子现金的分布式记账转账功能。而随着技术的不断发展更新,越来越多的人希望突破“账本”的限制,从而可以把这项未来技术应用在更广阔的领域。</span><br /><br /><span style="color:#333333;">以太坊(Ethereum)为代表的第二代区块链公链项目,就是其中的佼佼者。与比特币不同,以太坊的定位是一个“世界计算机”。以区块链作为底层存储技术,我们不仅可以记账转账,而且可以构建“智能合约”(smart contract)定义程序化的处理流程,进而实现区块链上运行的“去中心化应用”(DApp)。</span><br /><br /><span style="color:#333333;">以太坊项目自提出后就受到了广泛关注,快速的发展和壮大,而且由于其“分布式应用平台”而非“分布式账本”的定位,越来越多的开发人员开始以以太坊为基础设施,在上面开发DApp。</span><br /><br /><span style="color:#333333;">随着更多开发人员的参与,和项目的逐步落地,以太坊已成为从事区块链学习和开发不可或缺的一个环节;既了解区块链底层原理、又熟悉以太坊架构、还能基于以太坊开发DApp的专业人才,也成为了各大公司发力区块链技术储备的重点对象。</span><br /><br />【课程简介】<br /><span style="color:#333333;">本套以太坊课程,对以太坊基础理论知识和架构做了系统的梳理和深入的阐述,并对solidity和DApp的开发做了系统讲解,另外还对以太坊白皮书、黄皮书做了介绍;为有志于学习区块链技术、了解以太坊底层架构和DApp开发原理的工程师提供学习平台和帮助。</span><br /><br /><span style="color:#333333;">本教程内容主要分为五大部分:以太坊基础、以太坊原理和架构、以太坊编程及应用、合约工作流以及原理深入分析。</span><br /><br /><span style="color:#333333;">通过学习本套课程,可以使学习者对以太坊有充分的认识,对整个区块链技术有更深刻的理解,对区块链应用开发有更加整体的领悟。</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老刘 Edward

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值