我想知道用 Haskell 或 Idris 等类型语言表达智能合约的最佳方式是什么(例如,你可以编译它以在以太坊网络上运行)。我主要关心的是:捕获合约可以做的所有事情的类型是什么?
一个天真的解决方案是将合同定义为EthIO类型的成员。这种类型类似于 Haskell 的IO,但不是启用系统调用,而是包括区块链调用,即可以读取和写入区块链的状态,调用其他合约,获取块数据等。
EthIO
IO
-- incrementer.contract main: EthIO main = do x <- SREAD 0x123456789ABCDEF SSTORE (x + 1) 0x123456789ABCDEF
这显然足以执行任何合同,但是:
会太强大。
特别是与以太坊区块链非常耦合。
在这个想法下,合约将被定义为折叠一系列动作:
type Contract action state = { act : UserID -> action -> state -> state, init : state }
所以,一个程序看起来像:
incrementer.contract main : Contract main = { act _ _ state = state + 1, init = 0 }
也就是说,您定义初始状态、操作类型以及当用户提交操作时该状态如何更改。这将允许人们定义任何不涉及发送/接收资金的任意合同。大多数区块链都有某种货币,最有用的合约以某种方式涉及货币,所以这种类型的限制太强了。
我们可以通过将货币逻辑硬编码到上面的类型中来使上面的类型知道货币。因此,我们会得到类似的东西:
type Contract action state = { act : UserID -> action -> state -> state, init : state, deposit : UserID -> Amount -> state -> state, withdrawal : UserID -> Amount -> state -> Maybe state }
即,合约开发者需要明确定义如何处理货币存款和取款。这种类型足以定义任何可以与主机区块链货币交互的自包含合约。遗憾的是, 这样的合约无法与其他合约交互 。在实践中,合同经常相互影响。例如,交易所需要与其交换的 Token 合约进行通信以查询余额等。
因此,让我们退后一步,将保守的解决方案重写为:
type Contract = { act : UserID -> Action -> Map ContractID State -> State, init : State }
根据这个定义,该act函数不仅可以访问合约自己的状态,还可以访问同一区块链上所有其他合约的状态。由于每个合约都可以读取彼此的状态,因此可以轻松地在此之上实现通信协议,因此,这种类型足以实现任意交互的合约。此外,如果区块链的货币本身是作为合同实现的(可能使用包装器),那么该类型也足以处理货币,尽管它没有在类型上进行硬编码。但该解决方案有两个问题:
act
查看另一个合约的状态看起来像是一种非常“hacky”的通信方式;
以这种方式定义的合约将无法与不了解该解决方案的现有合约进行交互。
现在我在黑暗中。我知道我对这个问题没有正确的抽象,但我不确定它会是什么。 看起来问题的根源在于我无法正确捕捉跨合约通信的现象。 哪种具体类型更适合定义任意智能合约?
在Kindelia中,合约只是一个返回 IO 的函数,在网络上执行副作用操作,包括保存/加载持久状态,获取调用者名称和块高度,以及调用其他 IO。因此,只需通过输入调用它们来调用合约,然后它们就可以做任何他们需要的事情,就像一个正常的程序一样。5 年后,我不认为必须有不同的或花哨的方式来处理合同。下面是一个“计数器”示例:
// Creates a Counter function with 2 actions: ctr {Inc} // action that increments the counter ctr {Get} // action that returns the counter fun (Counter action) { // increments the counter (Counter {Inc}) = !take x // loads the state and assigns it to 'x' !save (+ x #1) // overwrites the state as 'x + 1' !done #0 // returns 0 // returns the counter (Counter {Get}) = !load x // loads the state !done x // returns it // initial state is 0 } with { #0 }
如果写入,它的类型将是Counter : CounterAction -> IO<U128>. 请注意,合约必须具有依赖类型,因为它们返回的类型可能取决于调用者正在执行的操作的值。
Counter : CounterAction -> IO<U128>
当然,不同的网络可能有完全不同的类型,或多或少有限制,但它们至少必须满足两个基本需求:持久化状态和与其他合约通信。