> For the complete documentation index, see [llms.txt](https://docs.sat20.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sat20.org/xie-yi-yu-an-quan/smart-contracts/template.md).

# 模板合约

模版合约是聪网原生智能合约类型。其执行逻辑由聪网节点源码中内置的Go runtime实现。

模版合约遵循[智能合约](https://github.com/sat20-labs/docs/tree/main/protocol/contracts/README.md)的通用协议。本文只定义模版合约特有规则。

## 合约类型

模版合约类型为：

```
ContractTypeTemplate
```

模版合约不执行EVM bytecode，不使用Solidity ABI，不维护EVM account state。模版合约也不是通道合约，不依赖RSMC通道、多方签名或承诺交易。

模版合约的资产控制边界来自合约VM状态和canonical `CONTRACT_RESULT`。

## 地址规则

模版合约使用通用合约地址格式：

```
ca/tc + version + ContractTypeTemplate + hash
```

模版合约地址hash长度为32字节。hash输入为：

1. `Contract.Encode()`得到的合约内容。
2. deployer。
3. 部署随机值。

部署随机值用于让相同deployer和相同合约内容生成不同合约地址。

模版合约runtime中的`URL()`等于合约地址。

## 部署规则

`CONTRACT_DEPLOY`用于部署模版合约。

部署payload包含：

1. 模版名称。
2. 模版版本。
3. deployer。
4. 部署随机值。
5. gas limit。
6. `Contract.Encode()`得到的合约内容。

节点根据部署payload生成合约地址、初始化runtime状态，并在同一区块内通过`CONTRACT_RESULT`记录部署结果和state root。

如果合约地址已存在，部署无效。

## 调用规则

`CONTRACT_INVOKE`用于调用模版合约。

调用交易必须输出到被调用合约地址。该输出同时作为：

1. Call TX和Result TX的绑定UTXO。
2. 本次调用转入合约的资产。
3. 本次调用和后续结算的费用来源。

调用者身份由调用交易最后一个输入解析得到。runtime中的用户状态、LP归属、refund权限和历史记录都使用该身份。

模版合约可以定义默认调用。默认调用由没有合约OP\_RETURN、但输出到模版合约地址的交易触发。合约根据输出中的资产类型和数量自行解释业务含义；如果该输出不符合合约可执行条件，合约可以将其视为无效业务输入而不改变业务状态。

## 费用规则

模版合约使用统一gas资产支付费用。

不同模版可以定义自己的调用费和撮合费。限价单和AMM模版沿用原通道合约的费用计算方式，但费用资产使用统一gas资产。

gas/funding输出中用于费用的部分不进入合约资产池。扣除费用后的剩余资产才作为合约可支配资产。

## Result TX规则

模版合约不接受外部提交的`CONTRACT_RESULT`进入mempool。

出块节点根据模版runtime结果构造canonical `CONTRACT_RESULT`。验证节点必须独立重放runtime，并比较区块中的Result TX。

如果Result TX缺失、输入输出不符合canonical规则，或与本地重放结果不一致，区块无效。

## 状态规则

模版合约状态属于全局合约VM状态。

模版合约state root、EVM合约state root和Agent合约state root合成统一的contract state root，并写入coinbase。

模版runtime必须满足：

1. 相同链上输入得到相同状态。
2. 不依赖本地时间、外部服务、随机数或其他非确定性数据。
3. 合约资产余额由合约地址UTXO集合和runtime状态共同验证。

## 第一阶段模版

第一阶段实现四类模版：

1. 限价单交易合约。
2. AMM交易合约。
3. 资产兑换合约。
4. 自动支付合约。

限价单和AMM模版从原通道合约业务逻辑迁移，但不保留以下能力：

1. 通道两端签名。
2. RSMC通道状态。
3. L1 deposit/withdraw。
4. L1 commit/reveal和BRC20 transfer inscription流程。
5. 老通道合约状态迁移。

自动支付合约不来自通道合约迁移，而是第一阶段新增的协议内置模版，用于把多个委托人的持续支付资金聚合在同一个合约中，并在每个区块生成一笔统一支付。

## 限价单交易合约

限价单交易合约对应原`SwapContractRuntime`的限价单交易逻辑。

接口名称和订单类型值与通道合约保持一致：

1. `swap`：挂买单或卖单。
2. `refund`：取消挂单或取回可退资产。
3. 买单订单类型为`2`。
4. 卖单订单类型为`1`。
5. refund订单类型为`3`。

撮合规则：

1. 买单按价格从高到低排序。
2. 卖单按价格从低到高排序。
3. 同价格按区块顺序、交易顺序和item id排序。
4. 只有买价大于或等于卖价时成交。
5. 成交价使用卖单价格。
6. 买单可以吃到更低价格卖单。
7. 每次撮合生成买家资产转移和卖家聪转移，两笔transfer位于同一个Result TX中。
8. 买单完成后，未使用的聪退回买家。
9. 卖单剩余资产不足以按价格成交时，剩余资产退回卖家。

refund规则：

1. `refund`参数包含item id列表。
2. item id列表为空时，取消调用者所有未完成挂单。
3. item id列表非空时，只取消指定item。
4. 调用者只能取消自己的挂单。
5. 已成交并已经通过Result TX发出的资产不再进入refund。

默认调用规则：

1. 向限价单合约输入聪时，合约按当前可成交的卖价解释为买入。
2. 向限价单合约输入该合约资产时，合约按当前可成交的买价解释为卖出。
3. 当前没有可参考成交价时，默认调用不产生新的订单或资产结算。

## AMM交易合约

AMM交易合约对应原AMM通道合约的swap和流动性逻辑。

接口名称和订单类型值与通道合约保持一致：

1. `swap`：AMM买入或卖出。
2. `refund`：取回可退资产。
3. `addliq`：添加流动性。
4. `removeliq`：移除流动性。
5. 买单订单类型为`2`。
6. 卖单订单类型为`1`。
7. 添加流动性订单类型为`9`。
8. 移除流动性订单类型为`10`。

部署和ready规则：

1. AMM合约部署内容包含资产名称、初始资产数量、初始聪数量和常数K。
2. 合约部署后，如果合约地址中的资产池、聪池或`asset*sats`没有达到部署参数要求，合约不进入可交易状态。
3. 合约可以通过`addliq`补充底池。
4. 当资产池、聪池和`asset*sats >= K`都满足后，合约进入可交易状态。
5. 合约ready后，正常交易过程中不再要求实时`asset*sats >= K`。
6. 如果池子被移空，合约退出可交易状态，必须再次通过`addliq`达到初始K后才能重新ready。

AMM swap规则：

1. AMM采用常数乘积公式。
2. AMM买入时，用户输入聪，输出资产。
3. AMM卖出时，用户输入资产，输出聪。
4. AMM卖出参数中的`Amt`表示最小可接受输出聪数量，输入资产数量以Call TX funding output为准。
5. AMM买入参数中的`Amt`表示最小可接受输出资产数量，输入聪数量以Call TX funding output为准；`UnitPrice`只能作为报价或滑点约束，不能作为输入金额的第二来源。
6. 滑点保护失败时，本区块直接生成refund result。
7. 同一区块内多笔swap按canonical交易顺序逐笔处理。每笔成交使用上一笔成交后更新的池子和K定价；输入资产扣除0.8%服务费后参与常数乘积计算，但池子余额加入完整输入，使服务费留在池中。买入参数`Amt`只表示最小可接受输出，满足滑点条件后整笔输入参与兑换，不按`Amt`截断成交或退回多余输入。
8. 同一区块内AMM先处理swap，再处理add/remove liquidity，以对齐原通道合约。
9. 因addliq达到ready的区块不会同时撮合之前等待中的swap，等待中的swap会在后续区块结算。

AMM默认调用规则：

1. 向AMM合约输入聪时，合约解释为买入。
2. 向AMM合约输入该合约资产时，合约解释为卖出。
3. 不符合AMM交易语义的默认调用不改变AMM业务状态。

流动性规则：

1. `addliq`调用的入池资产数量和入池聪数量以Call TX funding output为准。
2. `addliq`参数只应表达最小可接受LPT、比例约束、deadline等不能由funding output推导的约束，不应重复表达入池资产数量或入池聪数量。
3. 多余资产或多余聪按池子比例计算后通过Result TX退回。
4. `removeliq`参数包含要移除的LPT数量。
5. 如果请求移除的LPT超过调用者余额，按调用者实际LPT余额处理。
6. 移除流动性时，LP获得对应池子份额。
7. 产生利润时，LP获得60%利润；原服务端和基金会两部分合并后统一给基金会。
8. 当前实现使用部署者地址作为基金会接收地址。

## 资产兑换合约

资产兑换合约用于按部署者设定的规则出售库存资产A。invoker输入资产B后获得资产A，deployer在同一个canonical `CONTRACT_RESULT`中获得成交消耗的资产B。

部署内容包含：

1. 资产A名称。
2. 资产B名称。
3. 价格模式。
4. 价格阶梯表。

价格模式支持：

1. `height`：按区块高度选择当前价格阶梯。
2. `sold_a`：按累计已售出资产A数量选择当前价格阶梯。

价格阶梯表中的`bPerA`表示兑换1单位资产A需要支付的资产B数量。第一档阈值必须为0，后续阈值必须严格递增。

接口规则：

1. `exchange`：输入资产B并兑换资产A，参数可以包含最小可接受输出资产A数量。
2. `close`：仅deployer可调用，关闭合约并取回剩余资产A。
3. 无参数默认调用输入资产A时，视为补充库存。
4. 无参数默认调用输入资产B时，视为按当前价格兑换资产A。
5. 无参数默认调用同时输入资产A和资产B时，资产A先补充库存，资产B再用于兑换。

兑换规则：

1. 成交价由部署时的价格模式和当前区块状态确定。
2. 合约最多输出当前可用库存资产A。
3. 输入资产B数量以Call TX funding output为准；当库存资产A不足以满足输入资产B时，按当前价格部分成交，未使用的资产B退回invoker。
4. 当`exchange`参数中的最小输出资产A不满足时，净输入资产B退回invoker。
5. 合约关闭后不再接受新的兑换，后续兑换输入按失败处理。

gas同名规则：

1. 资产A或资产B都可以与统一gas资产同名。
2. 当gas资产等于资产A时，先从可用资产A中扣除本次Result费用，剩余资产A才可作为库存或兑换输出。
3. 当gas资产等于资产B时，先从本次输入资产B中扣除本次Result费用，剩余资产B才参与兑换。
4. 当gas资产不同于资产A和资产B时，gas按模版合约通用费用规则处理。

## 自动支付合约

自动支付合约用于把多个地址委托的持续支付资金聚合到同一个合约中，并按区块向一个接收地址或当前出块矿工支付费用。模版名称为：

```
autopay.tc
```

部署内容包含：

1. 服务名称。
2. 接收地址；为空时表示每个区块的支付作为矿工费用支付给当前出块矿工。
3. 支付资产名称，可以是聪或任意聪网资产。
4. 个人每区块最低支付额度。

部署内容不包含用户列表、支付区间、支付结束高度或按高度变化的曲线。每个委托人自己的支付额度和余额由后续调用写入runtime state。

调用接口：

1. `config`：委托人设置自己的每区块支付额度，额度不得低于合约最低额度。
2. `default invoke`：委托人向合约地址funding，增加自己的可支付余额；如果委托人没有配置额度，默认使用合约最低额度。
3. `cancel`：委托人取消自己的支付配置，并退回该委托人的剩余支付余额。
4. `close`：只有deployer可以关闭合约，并批量退回所有委托人的剩余支付余额。

支付资产可以是聪或任意聪网资产。如果支付资产本身就是gas资产，runtime需要在同一种物理资产中区分业务支付余额和合约trigger/result gas余额。

委托状态：

1. runtime为每个委托地址维护独立状态，包括每区块支付额度、funding余额、累计已支付额度、已支付区块数量、最近支付高度和委托状态。
2. 委托状态可以是active、funding或closed。
3. 合约总的可支付余额是所有委托人余额之和。
4. 合约状态可以对外返回聚合余额，也可以返回每个委托人的可审计状态。

激活和funding规则：

1. 部署交易或默认调用都可以向合约地址funding。
2. 部署交易的funding归属于deployer。
3. 默认调用的funding归属于该调用的invoker。
4. 当至少一个委托人余额足够支付其下一块费用，并且合约有足够trigger/result gas时，合约处于active状态。
5. 某个委托人余额不足支付一个区块时，该委托人进入funding状态，不参与该区块支付；其他余额充足的委托人仍可继续支付。
6. 如果所有委托人都无法支付，或合约缺少必要trigger/result gas，合约整体进入funding状态，等待后续funding。

支付规则：

1. 每个区块结算时，runtime扫描所有active委托人。
2. 对每个余额足够的委托人，扣除其每区块支付额度。
3. 出块节点自动生成一笔canonical `CONTRACT_RESULT`，把本区块所有委托人的应付金额合并为一个支付输出。
4. 如果接收地址不为空，支付输出转给该接收地址。
5. 如果接收地址为空，支付输出作为矿工费用进入本区块收益。
6. 一个区块只应为该自动支付合约生成一笔支付Result，避免大量委托人导致区块中出现过多自动trigger交易。

关闭规则：

1. 只有deployer可以关闭自动支付合约。
2. close时，合约按委托人分别退回各自剩余支付余额。
3. 每笔close Result最多包含1000个退款输出；如果委托人数量超过单笔上限，未退回的余额保留在合约地址，并在后续Result中继续处理。
4. close完成后，合约管理的剩余gas余额退回deployer。
5. 合约地址上不属于合约管理余额的其他资产，继续按通用合约framework的剩余资产处理规则执行。
