我想知道用 Haskell 或 Idris 等类型语言表达智能合约的最佳方式是什么(例如,你可以编译它以在以太坊网络上运行)。我主要关心的是:捕获合约可以做的所有事情的类型是什么?
天真的解决方案:EthIO
一个天真的解决方案是将合同定义为EthIO
类型的成员。这种类型类似于 Haskell 的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
函数不仅可以访问合约自身的状态,还可以访问同一区块链上所有其他合约的状态。由于每个合约都可以读取彼此的状态,因此可以轻松地在此之上实现通信协议,因此,这种类型足以实现任意交互的合约。此外,如果区块链的货币本身是作为合同实现的(可能使用包装器),那么该类型也足以处理货币,尽管它没有在类型上进行硬编码。但该解决方案有两个问题:
查看另一个合约的状态看起来像是一种非常“hacky”的通信方式;
以这种方式定义的合约将无法与不了解该解决方案的现有合约进行交互。
现在怎么办?
现在我在黑暗中。我知道我对这个问题没有正确的抽象,但我不确定它会是什么。看起来问题的根源在于我无法正确捕捉跨合约通信的现象。哪种具体类型更适合定义任意智能合约?