0x00 前言vue
基于NEO进行dapp开发的过程当中,基于UTXO的GAS并不方便做为dapp的代币使用,因而为了便于在DAPP中直接接入GAS,NEL颜值和技术担当李总开发了基于NEP5的SGAS合约,用于与GAS进行一比一兑换,搭建了应用合约与UTXO之间的桥梁。git
尽管SGAS开发的初衷是为了NNS域名竞拍系统的功能实现,可是李总在设计的时候便将SGAS定义为一种通用的NEP5合约,所以任何须要在DAPP中使用GAS做为燃料的合约均可以直接使用SGAS。github
0x01 获取SGAS安全
SGAS是NEP5的代币合约,自己并无发行量,只有当用户向SGAS合约地址转入GAS时,SGAS合约才会发行对应数量的SGAS代币,并把新发行的SGAS转给该用户。app
接下来的内容设计应用合约脚本的构建,建议阅读以前先了解下这方面的知识。测试
前文已经提到获取SGAS须要用户向SGAS合约地址充值指定金额的GAS,可是仅仅转帐是没有办法实现SGAS发行的,由于GAS转帐的过程是鉴权合约调用的过程,这个过程当中SGAS合约没法进行数据的写入,而SGAS发行代币的方式就是经过数据的记录,这也就意味着SGAS没有办法进行发行操做。为了解决这个问题,在转帐以后,还须要经过应用合约的调用过程来向用户地址解锁指定数量的SGAS,完成GAS兑换SGAS的过程。ui
var sb = new ThinNeo.ScriptBuilder(); sb.EmitParamJson([]); sb.EmitPushString('mintTokens'); sb.EmitAppCall(DAPP_SGAS); return sb.ToArray();
在构造交易的时候,传入构造的脚本,这样交易在完成了转帐以后会直接执行交易内的脚本。兑换SGAS的指令是 mintTokens ,SGAS合约在接收到这个指令后,会经过runtime获取交易中GAS的output,在对output进行验证和统计后,向该用户的帐户解锁对应数量的SGAS。设计
合约交易构造过程以下:code
let tran = Transfer.makeTran(target, asset, count); tran.type = ThinNeo.TransactionType.InvocationTransaction; tran.extdata = new ThinNeo.InvokeTransData(); //塞入脚本 (tran.extdata as ThinNeo.InvokeTransData).script = script; return await Transfer.signAndSend(tran);
target是SGAS合约地址,makeTran是合约交易中向交易中添加input和output的标准过程。 由于经过GAS兑换SGAS大部分仍是UTXO的操做,因此合约构造和操做起来并非很复杂,可是当经过SGAS兑换GAS的时候,因为NEP5的诸多限制,操做起来就复杂多了。接口
0x02 兑换GAS
经过SGAS兑换GAS这个过程,若是真正理解了每一步的原理和缘由,对于NEO合约的理解应该算是绝对的一大进步,我这么理解。我第一次看的时候懵懵懂懂,代码量也不大,本身为就看懂了,还跑了测试。可是当我用TS本身去写这块的调用代码的时候,才发现举步维艰,其操做之复杂,设计之精巧远超我想象。这也是为何我想把SGAS的调用单独写成一篇博客来介绍的缘由。
首先咱们须要明确几件事:
以上四条是没法违背的,在兑换GAS的过程当中,必须遵循。那如今就须要思考如下几个问题:
首先咱们分析第一个问题,SGAS合约如何转GAS。为了帐户的安全,NEO合约没法进行主动的UTXO转帐,必须由用户进行鉴权调用,执行转帐操做。也就是说,SGAS没法主动退回GAS给用户,必须由用户来从SGAS帐户转出GAS。
第二个问题如何控制转帐金额。有NEO帐户而且进行过NEO或者GAS转帐的童鞋应该知道,只要你有转帐权限,那么转帐的金额彻底是由你本身指定的,也就是说,只要你愿意,那你彻底能够转出一个帐户里的全部资产。可是对于SGAS合约来讲,这样就不行,一旦给了用户转帐的权限,咱们彻底没法依靠上帝保佑全部用户只会转出他但愿兑换的金额的GAS。
也就是说咱们如今咱们必须给用户转帐的权限,可是又须要限制用户转帐的金额。这怎么办?
同时,因为GAS自己是艺UTXO的形式存在,而UTXO自己又是没法分割,一次使用的,咱们没法保证对于每个用户但愿兑换的GAS额度都恰好有对应金额的GAS的UTXO存在,那咱们如何分割UTXO来构造出用户须要的UTXO呢?
固然,这些问题都是我在看到李总的解决方案以后,又经过几乎一行代码一行代码向印炜大神请教以后才整理出的。
接下来就开始分析李总对于这一系列几乎使得SGAS方案陷于不可行问题的解决方案。
第一步: 获取可用UTXO。
因为转帐操做不管如何都须要用户主动发起,因此用户必须首先获取SGAS合约地址的GAS的UTXO。
第二步:查询UTXO可用性。
多出这一步的缘由是因为兑换GAS操做的复杂性,在一个共识周期内没法完成全部操做,因此本轮共识极可能会有以前共识周期中已经被标记的UTXO存在,咱们须要在使用以前检测如下这个UTXO是否被标记过。
var sb = new ThinNeo.ScriptBuilder(); sb.EmitParamJson([output_hash]); sb.EmitPushString('getRefundTarget'); sb.EmitAppCall(DAPP_SGAS); return sb.ToArray();
第三步:拆分UTXO。
因为UTXO只能使用一次,每次被使用事后就会报废,所以咱们只能经过构造才能得到咱们须要数额的UTXO,经过SGAS合约给SGAS合约本身的地址转帐构造出用户须要的UTXO。同时用户须要记录下这个UTXO。
该步骤主体就是普通的转帐操做,直接构造ContractTransaction就能够,须要注意的地方是,这里用的input和指向的output地址都须要是SGAS合约地址。此外,在添加见证人的时候,除了须要添加用户签名以外,还须要添加sgas合约脚本为见证脚本。
let sb = new ThinNeo.ScriptBuilder(); sb.EmitPushString("whatever") sb.EmitPushNumber(new Neo.BigInteger(250)); tran.AddWitnessScript(SGASScript, sb.ToArray());
第四步:标记UTXO。
因为转帐GAS是个鉴权操做,因此咱们还须要在转帐以后进行一个合约调用来标记用户生成的对应UTXO,将这个UTXO标记为只能该用户领取。
标记UTXO自己是GSAS合约的工做,可是这部分功能还须要用户进行触发才能执行,在用户拆分UTXO的时候,须要构造一个脚原本进行SGAS合约的调用:
var sb = new ThinNeo.ScriptBuilder(); sb.EmitParamJson([address_hash]); sb.EmitPushString('refund'); sb.EmitAppCall(DAPP_SGAS); return sb.ToArray();
第五步:领取GAS。
用户在通过一个共识周期确认UTXO已经拆分完成而且标记完成以后就能够经过记录的UTXO来进行SGAS转帐,因为在SGAS应用合约执行期间用户地址就已经和该UTXO进行了绑定所以,在转帐的时候,只有该用户能够转走这个UTXO。
至此,SGAS兑换GAS完成。
光是看描述原理的篇幅都知道这个SGAS兑换GAS是多么复杂的一个过程,固然这也是由于目前NEO的一些限制致使的,若是将来NEO对DAPP开放更多的接口和权限,这个过程确定会大大简化。
不知道看到这里的人有没有对李总的清奇脑洞震撼到,这鉴权调用和应用调用翻来倒去,对此稍有一点不清楚都会云里雾里不知所云。尤为是当全部的代码都写在同一个文件里的时候,感受都是两种人格在战斗。
参考资料:
SGAS:https://github.com/NewEconoLab/neo-ns/blob/master/dapp_sgas/sgas.cs
ThinSDK-cs:https://github.com/NewEconoLab/neo-thinsdk-cs/blob/master/smartContractDemo/tests/nns/sgas.cs
nel-wallet-vue: https://github.com/NewEconoLab/nel-wallet-vue