【问题标题】:What is an appropriate type for smart contracts?什么是智能合约的合适类型?
【发布时间】:2017-07-02 00:24:23
【问题描述】:

我想知道用 Haskell 或 Idris 等类型语言表达智能合约的最佳方式是什么(例如,您可以编译它以在以太坊网络上运行)。我主要关心的是:捕获合约可以做的所有事情的类型是什么?

简单的解决方案:EthIO

一个天真的解决方案是将合约定义为EthIO 类型的成员。这样的类型就像 Haskell 的IO,但不是启用系统调用,而是包括区块链调用,即可以读取和写入区块链的状态,调用其他合约,获取区块数据等。

-- incrementer.contract

main: EthIO
main = do
   x <- SREAD 0x123456789ABCDEF
   SSTORE (x + 1) 0x123456789ABCDEF

这显然足以执行任何合同,但是:

  1. 太强大了。

  2. 特别是与以太坊区块链非常耦合。

保守的解决方案:事件溯源模式

在这个想法下,合约将被定义为折叠操作列表:

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 函数不仅可以访问合约自身的状态,还可以访问同一区块链上所有其他合约的状态。由于每个合约都可以读取彼此的状态,因此可以轻松地在此之上实现通信协议,因此,这种类型足以实现任意交互的合约。此外,如果区块链的货币本身是作为合同实现的(可能使用包装器),那么该类型也足以处理货币,尽管它没有在类型上进行硬编码。但该解决方案有两个问题:

  1. 查看另一个合约的状态看起来像是一种非常“hacky”的通信方式;

  2. 以这种方式定义的合约将无法与不了解该解决方案的现有合约交互。

现在怎么办?

现在我在黑暗中。我知道我对这个问题没有正确的抽象,但我不确定它会是什么。 看起来问题的根源在于我无法正确捕捉跨合约通信的现象。什么具体类型更适合定义任意智能合约?

【问题讨论】:

  • “[事件溯源风格]将允许定义任何不涉及发送/接收资金的任意合同。”为什么事件溯源不能处理汇款和收款?
  • @BenjaminHodgson 如果它使用我在此处发布的类型,编译的合约将如何能够从主机区块链发送/接收资金?它没有在定义中的任何地方指定如何处理传入的以太币,也没有在什么情况下合约应该将以太币发送出去......
  • 你读过 Pettersson 和 Edstrom 的硕士论文Safer smart contracts through type-driven development吗?它描述了使用其效果系统将以太坊合约嵌入到 Idris 中。
  • 或许bitcoin community对此有更多想法

标签: haskell functional-programming idris ethereum smartcontracts


【解决方案1】:

在回答主要问题之前,我将尝试更准确地定义在 Haskell 或 Idris 中编写代码并编译它以在类似以太坊的区块链上运行的含义。 Idris 可能更适合这个,但我将使用 Haskell,因为这是我熟悉的。

编程模型

总的来说,我可以设想两种使用 Haskell 代码为区块链虚拟机生成字节码的方法:

  • 将 EVM 字节码构建为 Haskell 数据的

  • 一个从 Haskell 代码生成字节码的 GHC 后端

使用 library 方法,将为每个 EVM 字节码声明构造函数,并在其上分层库代码以创建程序员友好的构造。这可能会被构建成一元结构,从而为定义这些字节码提供类似编程的感觉。然后将提供一个函数来将此数据类型编译为适当的 EVM 字节码,以适当地部署到区块链。

这种方法的优点是不需要额外的基础设施 - 编写 Haskell 代码,使用股票 GHC 编译它,然后运行它来生成字节码。

最大的缺点是,从库中重用现有的 Haskell 代码并不容易。所有代码都必须针对 EVM 库从头开始编写。

这就是 GHC 后端 变得相关的地方。一个编译器插件(目前它可能必须是一个 GHC 分支,就像 GHCJS 一样)会将 Haskell 编译成 EVM 字节码。这将对程序员隐藏单个操作码,因为它们确实太强大而无法直接使用,而是将它们降级为由编译器基于代码级构造发出。您可以将 EVM 视为不纯、不安全、有状态的平台,类似于 CPU,语言的工作就是将其抽象出来。相反,您可以使用常规的 Haskell 函数式样式来编写它,并且在后端和您自定义编写的运行时的限制内,现有的 Haskell 库将编译并可用。

还有混合方法的可能性,我将在本文末尾讨论其中的一些。

在本文的其余部分,我将使用 GHC 后端方法,我认为这是最有趣和最相关的。我确信核心思想会延续到图书馆的方法中,也许会进行一些修改。

编程模式

然后您需要决定如何针对 EVM 编写程序。当然,可以编写常规的纯代码并进行编译和计算,但也需要与区块链进行交互。 EVM 是一个有状态的命令式平台,因此 monad 是一个合适的选择。

我们将这个基础 monad 称为 Eth(尽管它不一定是特定于以太坊的),并为其配备一组适当的原语,以安全和功能性地利用底层 VM 的全部功能风格。

稍后我们将讨论需要哪些原始操作,但目前,有两种方法可以定义这个 monad:

  • 作为具有一组操作的内置原始数据类型

    -- Not really a declaration but a compiler builtin
    -- data Eth = ...
    
  • 由于 EVM 的大部分类似于普通计算机,主要是它的内存模型,一个偷偷摸摸的方法是将其别名为 IO

    type Eth = IO
    

    在编译器和运行时的适当支持下,这将允许现有的基于IO 的功能(例如IORefs)在未经修改的情况下运行。当然,很多IO 功能(例如文件系统交互)将不受支持,并且必须在没有这些功能的情况下提供自定义base 包,以确保使用它们的代码不会编译。

原语

需要定义一些内置值来支持区块链编程:

-- | Information about arbitrary accounts
balance  :: Address -> Eth Wei
contract :: Address -> Eth (Maybe [Word8])
codeHash :: Address -> Eth Hash

-- | Manipulate memory; subsumed by 'IORef' if the 'IO' monad is used
newEthVar   :: a -> Eth (EthVar a)
readEthVar  :: EthVar a -> Eth a
writeEthVar :: EthVar -> a -> Eth ()

-- | Transfer Wei to a regular account
transfer :: Address -> Wei -> Eth ()

selfDestruct :: Eth ()

gasAvailable :: Eth Gas

其他基本功能,包括函数调用,包括决定调用是常规(内部)函数调用、消息调用还是委托消息调用,将由编译器和运行时处理。

智能合约类型

我们现在要回答最初的问题:智能合约的合适类型是什么?

type Contract = ???

合同需要:

  • 在 EVM 上执行代码 - 在 Eth monad 中返回一个操作
  • 调用其他合约。稍后我们将定义一个 Eth 操作来执行此操作。
  • 获取和返回值,类型为 inout
  • 访问有关其环境的信息,包括:
    • 当前交易转帐金额
    • 当前帐户、发送帐户和发起帐户
    • 区块信息

因此,适当的类型可能是:

newtype Contract in out = Contract (Wei -> Env -> in -> Eth out)

Wei 参数仅供参考;实际转账发生在合约调用时,不能被合约修改。

关于环境信息,决定什么应该作为参数传递以及什么应该作为原始Eth 操作提供,这有点像判断调用。

可以使用合约调用原语来调用合约:

call :: Contract in out -> Wei -> in -> Eth out

当然这是一个简化;例如,它不会 curry 输入类型。据推测,编译器将为每个可见合约生成独特的操作,类似于 Solidity。甚至可能不适合提供此原语。

一个额外的细节:EVM 支持 构造函数,将在创建合约时执行的 EVM 代码,以允许使用环境信息。因此,由程序员编写的合约类型将是:

main :: Eth (Contract in out)
main = return . Contract $ \wei env a -> do
  ...

结论

我省略了许多细节,例如错误处理、日志记录/事件、Solidity 互操作/FFI 和部署。尽管如此,我希望我已经对针对区块链智能合约环境的函数式语言的编程模型进行了有用的概述。

这些想法并不是严格意义上的以太坊特有的;但是,请注意以太坊使用基于帐户的模型,而比特币和卡尔达诺都使用Unspent Transaction Output (UTxO) 模型,因此许多细节会有所不同。比特币并没有真正可用的智能合约平台,而 Cardano(其智能合约功能在撰写本文时处于后期测试阶段)完全在 Plutus 中编程,Plutus 是 Haskell 的变体。

除了严格的基于库或后端的方法来生成 EVM 字节码,还可以设计其他更用户友好的方法。 Plutus,卡尔达诺区块链语言,使用模板 Haskell 拼接将链上 Haskell 代码嵌入到链下执行的普通 Haskell 中。然后,此代码由 GHC 插件处理。

另一个有趣的想法是使用 Conal Eliot 的 compiling to categories 为区块链提取和编译 Haskell 代码。这也使用了编译器插件,但必要的插件已经存在。所有这些都是定义相关类别理论类型类的实例所必需的,并且您可以免费获得 -Haskell-to-arbitrary-backend 编译。

进一步阅读

在写这篇文章时,我大量参考了以下参考资料:

其他有趣的资源:

【讨论】:

  • 这是在 Haskell 中编写以太坊合约的两种直接方式,就像今天一样。但我相信最初的问题的目的是探索可以通过对合同使用更丰富的类型来强制执行的不变量/安全保证,如您的 通过 TDD 实现更安全的智能合同参考。
  • 我很喜欢使用 CCC 实现编译的想法!
【解决方案2】:

两个建议的解决方案:

type Contract action state = {
    act  : UserID -> action -> state -> (state, Map ContractID State)
}

地图用于跟踪同一区块链上其他合约的状态。该解决方案有两个优点:非常简单,不需要任何特殊的语言特性,并且可以与不了解该解决方案的现有合约进行交互。或者只是:

type Contract action state = {
    act  : UserID -> action -> state -> (state, List State)
}

无论哪种方式,都使用不知道区块链货币的类型。这种类型捕获了一个合约可以执行的所有可能的操作,以及它如何改变自己的状态以及同一区块链上其他合约的状态。它还捕获用户提交操作时发生的情况。最后一部分对于实现智能合约之间的通信协议至关重要。例如,如果我们想实现一个交易所合约,那么我们需要知道当一个交易所提交订单而另一个交易所接受或拒绝它时会发生什么。该信息需要从第二个合同传回给第一个合同,以便在此类提交/接受/拒绝事件发生后他们都同意各自的状态。因此,这种类型将捕获有关跨合约通信的所有必要信息,此外还能够定义任意智能合约逻辑,而无需将有关以太坊货币的任何特定内容硬编码到其中(甚至根本不知道是否存在某种货币)。

【讨论】:

    猜你喜欢
    • 2020-06-01
    • 2023-02-09
    • 2017-12-18
    • 1970-01-01
    • 2019-10-23
    • 2021-12-28
    • 1970-01-01
    • 2023-01-25
    • 2022-12-06
    相关资源
    最近更新 更多