1. 字节雪球爱好者社区首页
  2. 技术应用

OByte自治代理编程指南(一):入门

自治代理(Autonomous Agents, AA)是OByte的DAG账本上的一类特殊地址(账户),它以预定的程序进行工作。自治代理的行为特性类似于一台接受投币的自动售货机,接受投币后由键盘输入数据指令,然后做出相应的动作,例如冲一杯咖啡、播一首歌、或者其它已设定好的动作。

自治代理可以用于在OByte平台上迅速构建去中心化金融应用。

基本语法

自治代理的代码在OByte上部署之后就永远不会更改。代码内容是开放的,且由网络上的所有节点执行。

任何人都可以通过向自治代理的地址发送交易来激活它。 这是一个简单自治代理的例子:

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {address: "{trigger.address}", amount: "{trigger.output[[asset=base]] - 1000}"}
        ]
      }
    }
  ]
}]

messages是自治代理生成的响应交易的模板。它遵循常规OByte交易的结构,但它的花括号{}之间的部分生成的响应交易依赖于触发(激活)交易的内容。这类似于如何将PHP代码插入HTML,从而使结果页面根据请求来生成。编写自治代理的语言称为Oscript。

上面的自治代理示例是将收到的资金(减去1000字节手续费)发送回发送方。trigger.address是激活自治代理的发送方地址。trigger.output允许查找发送给自治代理的不同资产的金额,trigger.output [[asset = base]]是基础货币bytes。

另一个例子是以1.5 tokens/byte的价格出售代币:

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY=', // this is the token being sold
        outputs: [
          {address: "{trigger.address}", amount: "{ round(trigger.output[[asset=base]] * 1.5) }"}
        ]
      }
    }
  ]
}]

在使用上述自治代理之前,需要将自治代理的代码部署在OByte上面。访问oscript.org,复制粘贴上面的代码,然后单击“Deploy”,你将获得自治代理的地址。任何人现在都可以发送交易到这个地址来触发执行自治代理的代码。

参数传递

自治代理可以根据它在触发交易中收到的数据来生成响应交易。

在OByte中,任何交易都可以包含一个或多个不同类型的消息。消息类型字段为app。最常见的消息类型是payment。每个交易必须包含至少一个payment类型消息,用来支付存储交易的手续费。另一种消息类型是data。这种消息类型提供了一个可以包含任何数据的json对象:

{
  app: 'data',
  payload: {
    withdrawal_amount: 25000,
    nested_array: [
      99,
      {another_field: 44}
    ],
    nested_object: {yet_another_field: "value2"}
  },
  ...
}

payload中的json对象是交易发送的数据。

当自治代理在触发交易中收到data类型消息(当然还有payment类型消息)时,它可以通过trigger.data访问交易中发送的数据,其动作可以取决于收到的数据:

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {address: "{trigger.address}", amount: "{trigger.data.withdrawal_amount}"}
        ]
      }
    }
  ]
}]

上面的自治代理尝试将trigger.data.withdrawal_amount字节发送回发送方。withdraw_amount是触发交易的data类型消息中的字段:

{
  withdrawal_amount: 3000
}

错误处理

在上面的示例中,如果withdraw_amount大于自治代理的余额(包括从触发交易接收的金额),则自治代理将执行失败。当自治代理因某种原因未能处理触发交易时,它会回滚所有更改并尝试将所有收到的资金(包括资产)退回给发送方,但需要减去发送费用。默认情况下,基础资产的退回费用为10000字节,所有其它资产的退回费用为0。这也是最低限度。可以通过在自治代理定义中添加bounce_fees字段来覆盖默认值:

['autonomous agent', {
  bounce_fees: {
    base: 20000,
    "n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY=": 100
  },
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {address: "{trigger.address}", amount: "{trigger.data.withdrawal_amount}"}
        ]
      }
    }
  ]
}]

如果必须回滚触发交易,上述自治代理将收取20000字节和100个单位的资产n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY=。 向自治代理发送少于20000个字节将导致自治代理吞掉相应资产(回滚费用也不够)。当资产n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY=被发送到自治代理时,它也应该至少为100,否则自治代理会吞掉相应资产。

发送数据消息

如上所述,payment是最常见的消息类型,但是其它类型的消息也可以通过任何地址发送,包括自治代理。

发送data_feed消息

自治代理可以作为预言机(oracle)发送data_feed消息:

['autonomous agent', {
  messages: [
    {
      app: 'data_feed',
      payload: {
        "{trigger.data.feed_name}": "{trigger.data.feed_value}"
      }
    }
  ]
}]

上面示例展示了数据对象的键也可以通过Oscript进行参数化,就像对象的值一样。

上述自治代理没有payment消息,但会自动添加用来支付发送手续费。

发送data消息

自治代理可以发送任何结构化数据:

['autonomous agent', {
  messages: [
    {
      app: 'data',
      payload: {
        forwarded: 1,
        initial_data: "{trigger.data}"
      }
    },
    {
      app: 'payment',
      payload: {
      asset: 'base',
        outputs: [
          {address: "JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY", amount: "{trigger.output[[asset=base]] - 1000}"}
        ]
      }
    }
  ]
}]

data_feed消息不同,data消息:

  • 可以具有任何嵌套深度的结构(data_feed是键值对);
  • 没有进行搜索索引;
  • 可以向其它自治代理发送数据。

上述自治代理将收到的资金(减去1000字节手续费)转发到另一个地址,该地址可能是其它自治代理。它还可以传递数据,其中包括接收的数据参数。trigger.data的值将扩展为一个对象,并作为initial_data字段的值添加。

如果接收者(JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY)恰好是另一个自治代理,则只有在当前自治代理的执行完成后才会执行,以保证没有双花问题。

发送text消息

['autonomous agent', {
  messages: [
    {
      app: 'text',
      payload: `{"Hello, " || trigger.data.name || "!"}`
    }
  ]
}]

上面自治代理将会发送一个简单的文本消息,并将其存在DAG上。||是字符串拼接运算。

定义新资产

['autonomous agent', {
  messages: [
    {
      app: 'text',
      payload: {
        cap: "{trigger.data.cap otherwise ''}",
        is_private: false,
        is_transferrable: true,
        auto_destroy: "{!!trigger.data.auto_destroy}"
        fixed_denominations: false,
        issued_by_definer_only: "{!!trigger.data.issued_by_definer_only}",
        cosigned_by_definer: false,
        spender_attested: false,
        attestors: [
          "{trigger.data.attestor1 otherwise ''}",
          "{trigger.data.attestor2 otherwise ''}",
          "{trigger.data.attestor3 otherwise ''}",
        ]
      }
    }
  ]
}]

上面自治代理根据触发交易中的data数据参数来定义新的资产。

其中,有些第一次出现的运算符:

  • !是逻辑非运算;
  • otherwise是条件运算符,当第一个参数为真时(除了false, 0和空字符串之外)返回第一个参数,否则返回第二个参数;
  • 当对象或数组值的计算结果为空字符串时,将删除相应的对象或数组元素。这意味着如果未传递cap参数,则payload中的cap为空字符串,因此将从资产定义中删除,并定义具有无限供应的资产;
  • 值为空的数组或对象字段也将被删除。因此,如果没有设置attestor1attestor2attestor3字段,则attestors数组将变为空,并将从最终定义中排除。

状态查询

自治代理的行为还可以依赖于描述整个账本的各种状态变量。

余额

['autonomous agent', {
  messages: [
    {
      app: 'payment', 
      payload: { 
        asset: 'base',
        outputs: [
            {address: "{trigger.address}", amount: "{ round(balance[base]/2) }"}
        ]
      }
    }
  ]
}] 

其它自治代理(非普通地址)的余额也可以查询:

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: `{ (balance["JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY"][base] < 1e6) ? "JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY" : trigger.address }`,
            amount: `{ trigger.output[[asset=base]] - 1000 }`
          }
        ]
      }
    }
  ]
}]

如果余额小于1,000,000字节,则上述自治代理将收到的任何付款(减小1000字节手续费)转发给另一个自治代理JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY,否则返回发送方。

数据订阅

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: `{(data_feed[[oracles='TKT4UESIKTTRALRRLWS4SENSTJX6ODCW', feed_name='BROOKLYNNETS_CHARLOTTEHORNETS_2019-07-21']] == 'CHARLOTTEHORNETS') ? "7XZSBB32I5XPIAVWUYCABKO67TZLHKZW" : "FDZVVIOJZZVFAP6FLW6GMPDYYHI6W4JG"}`
          }
        ]
      }
    }
  ]
}]

如果夏洛特黄蜂队获胜,则上述自治代理支付给7XZSBB32I5XPIAVWUYCABKO67TZLHKZW,否则支付给FDZVVIOJZZVFAP6FLW6GMPDYYHI6W4JGTKT4UESIKTTRALRRLWS4SENSTJX6ODCW是发布体育赛事结果的预言机地址。

在预言机发布数据之前,任何触发自治代理的交易都将失败并回滚。

请注意,输出中的amount字段被省略,这意味着将支付自治代理的所有余额。

认证

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: "{trigger.address}",
            amount: `{ (attestation[[attestors='JEDZYC2HMGDBIDQKG3XSTXUSHMCBK725', address=trigger.address]].reputation >= 50) ? 50000 : 0 }`
          }
        ]
      }
    }
  ]
}]

如果发送方地址由Steem认证机器人JEDZYC2HMGDBIDQKG3XSTXUSHMCBK725认证并且他的Steem声誉至少为50,则上述自治代理向发送方支付50000字节,否则支付0字节。尝试支付0字节金额会导致该输出被删除。由于这种情况下唯一输出被删除,因此对生成的响应交易不会产生任何影响,也就根本不会创建它。

变量

状态变量

自治代理可以在多次调用之间保存自己的状态。使用var来访问和分配状态变量:

['autonomous agent', {
  messages: [
    {
      if: `{ var['previous_address'] }`,
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: "{ var['previous_address'] }",
            amount: `{ trigger.output[[asset=base]] - 1000 }`
          }
        ]
      }
    },
    {
      app: "state",
      state: `{
        var['previous_address'] = trigger.address;
      }`
    }
  ]
}]

上述自治代理保存之前发送触发交易的地址,并在下一次调用时将收到的资金(减去1000字节手续费)发送给前一个用户。

只有在消息类型为state的特殊消息中才允许分配状态变量。此消息必须具有一个名为state的字段,该字段是一组Oscript语句,允许这些语句分配状态变量。状态消息不会被包含在最终响应交易中,它只能通过修改状态变量来影响自治代理的状态。

当访问未初始化的var变量,其值返回false

上面的示例在第一条消息中有一个if字段。 当var ['previous_address']尚未初始化(第一次调用)时,if的计算结果为false,第一条消息内容不出现在响应消息中。

其它自治代理的状态变量也可以读取,但是不能写入:

var['JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY']['var_name']

局部变量

在脚本执行过程中,局部变量可以用来存储中间结果:

['autonomous agent', {
  messages: [
    {
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: "{ trigger.address }",
            amount: `{
              $amount1 = trigger.output[[asset=base]] - 5000;
              $amount2 = round(balance[base] * 0.9);
              max($amount1, $amount2)
            }`
          }
        ]
      }
    }
  ]
}]

局部变量名称以$为前缀。 与状态变量不同,它们不会在多次调用之间保存。

为了更容易确定局部变量的值,有一个在其他语言中不常见的限制:单一赋值规则。局部变量只能赋值一次,并且该值在脚本结束前保持不变。尝试重新分配值将使脚本失败并使自治代理回退。

条件字段

正如我们之前在状态变量示例中看到的那样,自治代理生成的响应交易的某些部分可以通过添加if字段进行判断。如果if中的Oscript语句计算为true或任何真值(除false0和空字符串之外的任何内容),则其后面的对象将包含在最终响应交易中,否则将被排除。

if字段本身当然总是会从最终响应交易中删除。

初始化字段

if字段一样,任何对象都可以有一个包含初始化代码的init字段:

['autonomous agent', {
  messages: [
    {
      if: `{ trigger.data.pay }`,
      init: `{
        $amount1 = trigger.output[[asset=base]] - 5000;
        $amount2 = round(balance[base] * 0.9);
      }`,
      app: 'payment',
      payload: {
        asset: 'base',
        outputs: [
          {
            address: "{ trigger.address }",
            amount: `{
              max($amount1, $amount2)
            }`
          }
        ]
      }
    }
  ]
}]

与大多数其他脚本不同,init代码只包含语句,它不返回任何值。 另一个例外是上面状态变量中描述的状态消息,它也只是语句。 所有其他脚本可以包含0个或更多语句,但必须以表达式(不带;)结束,其值将成脚本的返回值。

init代码在if之后(如果它存在并且当然为真),且在其他语句立即执行。它通常用于设置稍后使用的局部变量。

条件和初始化字段中的局部变量

ifinit中设置的局部变量可以用于当前和嵌套对象的所有其他脚本中。在上面的示例中,$amount1$amount2在消息对象的init中设置,并且它们用在此消息的payload/outputs/output-0/amount中。

if中设置的局部变量也可在相应的init中使用。

两种类型的脚本

如上所述,有两种类型的脚本:仅包含语句的和具有返回值的。

只有两个仅包含语句的脚本:initstate脚本。这是我们之前见过的init脚本的示例:

{
  init: `{
    $amount1 = trigger.output[[asset=base]] - 5000;
    $amount2 = round(balance[base] * 0.9);
  }`,
  ...
}

所有其它的脚本都具有返回值。它们要不包含一个单独的表达式:

{
  address: "{ trigger.address }",
  ...
}

或者一组语句,并在结尾加上一个表达式:

{
  amount: `{
    $amount1 = trigger.output[[asset=base]] - 5000;
    $amount2 = round(balance[base] * 0.9);
    max($amount1, $amount2)
  }`,
  ...
}

表达式的计算结果就是脚本的返回值。

匹配字段

当你需要根据输入和环境参数在几个互斥选项中进行代码执行时,可以使用cases

['autonomous agent', {
  messages: {
    cases: [
      {
        if: "{trigger.data.define}",
        messages: [
          // first version of messages
          ....
        ]
      },
      {
        if: "{trigger.data.issue}",
        init: `{
          $amount = trigger.output[[asset=base]];
        }`,
        messages: [
          // second version of messages
          ....
        ]
      },
      {
        messages: [
          // default version of messages
          ....
        ]
      }
    ]
  }
}]

常规messages数组被替换为一个名为cases的数组。cases是一个列出几个互斥选项的数组。除了最后一个元素之外的每个选项都必须有一个if字段。 if为真的第一个选项定义了消息字段的消息。

各个选项中的if条件可能不是互斥的,但cases列表中前面列出的选项优先级更高。最终只会有一个选项被选择。

在上面的示例中,如果trigger.data没有define字段但有issue字段,则选择第二个选项,消息对象将变为:

['autonomous agent', {
  messages: [
    // second version of messages
    ....
  ]
}]

cases可以嵌套。以上关于ifinit和局部变量的所有内容也适用于cases

条件语句

上面的例子展示了如何在响应消息模板的不同部分分支执行。

使用if/else可以分支执行Oscript代码本身:

if (var['started']){
  $amount = balance[base];
  $current_winner = var['current_winner'];
}
else{
  $amount = trigger.output[[asset=base]];
  $current_winner = trigger.address;
}

返回语句

如果要中断脚本的执行并返回(可以返回或不返回值),请使用return语句。

以下是在具有返回值的脚本中使用return的示例,它必须返回一个值:

$amount = trigger.output[[asset=base]];
if ($amount > 1e6)
  return $amount;
// here comes some other code that will not be executed if $amount > 1e6

在仅包含语句的脚本(例如init)中,return不返回任何值,它只是中断脚本:

$amount = trigger.output[[asset=base]];
if ($amount > 1e6)
  return;
// here comes some other code that will not be executed if $amount > 1e6

回滚语句

如果自治代理发现错误情况并希望提前停止执行并回滚交易,请使用bounce

$maturity = var['maturity_timestamp'];
if (timestamp < $maturity)
  bounce("too early");

在此示例中,timestamp是当前时间戳(自1970年1月1日00:00:00 UTC以来的秒数)。

所有应用的更改都将被回滚,任何收到的资金将被退回给发送方(减去回滚费用)。

版权所有。发布者:Alan During,转载请注明出处:https://bbfans.org/2019/07/21/obyte-aa-guide1/

发表评论

登录后才能评论

联系我们

加入ByteBall技术群请添加

QR code