CKB的TypeScript的一种编程模式

现有的CKB在编写TS(TypeScript)时,因为TS中可能涉及到很多方法,通常会有两种方式来判断当前TS是处理哪种方法,1)是判断交易的结构是否满足某种形式,2)通过在Witness里添加方法名。

这两种方法都有弊端,方式1的弊端是,如果业务特别复杂,可能会存在两种业务的结构都是类似的,这种情况下单纯用方式一可能就无法处理,还需要增加另外的IF-Condition来判断,代码最后都比较复杂,也比较丑陋难懂。

方法2的处理方式,比较取巧,但是witness本意也不是用来存方法名的,而且存在被替换的风险。
这两种方法都存在一个比较大的缺点,就是在通过浏览器去查看交易时,从用户和浏览器角度讲都很难判断这个交易是做什么的。对于以太坊可以轻松的识别这个交易的意图,但是对于CKB的XUDT合约,用户需要仔细验证交易数据来判断,对于开发者和普通用户都很不友好。
截屏2025-07-04 15.04.26

这里的区别在于eth的交易中包含了交易方法名,而UTXO的交易中没有。CKB是否也可以做到呢?在实践中,我们观察到,TS的代码往往都是IF-Condition结构的,就是IF是某个Action,然后判断是否满足条件1,2,3。对于其他的Action在当前交易执行时,实际上并没有执行到这些代码,真正被执行的只有这一部分。我们考虑是否可以将每个Action提取为一个单独的TS呢?而多个Action共用一个状态。这样设计带来最大的几个好处:
1)不需要匹配交易结构或者使用Witness。
2)一个TS只负责一个Action,职责单一。
3)浏览器和用户可以识别交易。

经过实践后,是可行的。这里我们简单描述下我们这种编程模式的用法。我们将TS分为Action TS和State TS。State TS负责存储状态数据。Action TS没有状态可以随时生成,然后和State TS配合使用。

对于状态TS主要定义数据的存储格式,以及它可以和哪些Action TS共同执行,在状态TS中Args中存储了它允许的Action script 的script hash。状态TS需要满足条件:
1)在Inputs或Outputs中只有且必须只有一个对应的Action Script。

在Action Script中,它的Script args中需要传入状态TS的script hash,需要满足:
1) 如果Inputs/Outputs(这里要看具体情况是哪个)中没有状态TS则不执行任何操作,直接返还成功0。这个条件要在最前面进行执行。

在实际使用时,我们允许凭空产生或销毁一个Action Script Cell,例如Input是一个CKB Cell,然后Output就是这个Action Script,这个Action Script是无状态的,它没有Cell Data。也可以配合状态合约,在Output中产生一个Action Script。

Action Script Cell可以和状态共同使用,举一个最简单的例子,转账,我们的转账要求Input的总额 = Output,即转帐和销毁的逻辑不混合在一起。Input1 是A的状态cell,input2是转账Acton Script,Outputs是B的状态Cell,和A的状态Cell。输出中我们可以根据情况继续保留这个Action Script,这样可以复用。

交易逻辑容易搞混是常见的ckb 编程的一个弊端,因为合约中多个Action Script的逻辑混合在一起,有时候交易执行成功了,但是可能并不是我们想要的。比如A给B转账100,可能在交易构造时,不小心忘记找零了,就会导致A的余额凭空消失了。实际上是同时执行了转账和销毁。

经过实践我们发现这种模式带来的好处还有:
1)验证更严格。(因为不会存在几种不同的Action 混在一起,明确的执行目的)
2)单个Action script的脚本代码量非常少,代码更清晰,逻辑更简单,也更容易测试。项目逻辑越复杂,这个优点越明显。
3)通过浏览器很容易判断这个交易是什么交易,只要看Action script即可。

细心的朋友可能发现一个问题,在我们的设计中,两个类型的Script 的args都有对方的script hash,这个如何做到呢?

这里的解决方案是,使用TypeID,先准备好生成所有脚本的TypeID的Input,然后计算出TypeID Hash,由于TypeID可以预先计算好,这个时候再将TypeID硬编码到所有的Script代码中,而不是放到参数中,这样在每一份合约实例实际上使用的不同的Script 代码,原来的参数已经硬编码到代码中,所以可以直接部署。

另外一种方式是将其中一种Script args里的script hash换成code hash,这样的话,先生成其中一个A Script,其参数中使用的是其他Script 的code hash,然后计算出script hash,作为另外一个B Script中的参数。这种情况存在被攻击的风险,但是可以在测试的时候使用,方便编写测试代码。

2 Likes