原英文文档: https://book.clarity-lang.org/ch08-02-smart-claimant.html
在学习本章节内容时建议已经掌握了上一个章节内容: https://book.clarity-lang.org/ch08-01-time-locked-wallet.html
智能索赔 Smart claimant
我们在上一节中创建的时间锁钱包非常简单。但是想象一下,在部署合约程序之后,受益人希望将余额分配给多个不同的受益人。也许它是多年前由一位老亲属部署的,受益人现在想与其他家人分享。不管是什么原因,我们显然不能简单地回去更改或重新部署时间锁钱包程序。在某个时间点它会自动解锁,之后唯一的受益人可以获得钱包的全部余额。如何解决这个问题呢?我们可以创建一个最小化的临时智能合约作为受益人!它将调用 claim 函数,如果成功,则将代币平均分配给列表上的委托人。
本练习的重点是展示智能合约程序如何相互交互,以及如何增强功能或缓解旧合约程序的问题。
临时智能合约程序示例
我们会将 smart-claimant 合约程序添加到现有的时间锁钱包项目中,以便于开发和测试。导航到它并使用 clarinet contract new smart-claimant 添加它。
在这个例子中,我们假设有四个受益人。我们将采用 Clarinet 配置中定义的钱包地址 1 到 4 。 (如果您的 Clarinet 配置不同,请修改地址如下。)
+------------------------------------------------------+---------+
| ST1J4G6RR643BCG8G8SR6M2D9Z9KXT2NJDRK3FBTK (wallet_1) | 1000000 |
+------------------------------------------------------+---------+
| ST20ATRN26N9P05V2F1RHFRV24X8C8M3W54E427B2 (wallet_2) | 1000000 |
+------------------------------------------------------+---------+
| ST21HMSJATHZ888PD0S0SSTWP4J61TCRJYEVQ0STB (wallet_3) | 1000000 |
+------------------------------------------------------+---------+
| ST2QXSK64YQX3CQPC530K79XWQ98XFAM9W3XKEH3N (wallet_4) | 1000000 |
+------------------------------------------------------+---------+
自定义 Claim 函数
Clarity 非常适合创建小型临时智能合约程序。与其想出一个复杂的机制来添加和删除受益人,还不如把问题简单化,把应该收到一部分余额的人放在同一个房间里,并且钱包即将解锁。他们可以见证当前受益人创建合约程序并直接提供他们的钱包地址。
该自定义 claim 函数将会:
调用时间锁钱包的 claim 函数,如果失败则退出。
读取当前合约程序的余额。我们不会读取时间锁钱包的余额,因为有人可能已经错误地向 smart-claimant 发送了一些代币。我们也想包括这些代币。
通过将总余额除以接收人数量来计算每个接收人的均等的份额。
将计算出的份额发送给每个接收人。
在出现舍入错误的情况下转移余数。 (请记住,整数没有小数点。)
把它作为一个单一的 hard-coded 函数进行处理是很容易的。
(define-public (claim)
(begin
(try! (as-contract (contract-call? .timelocked-wallet claim)))
(let
(
(total-balance (as-contract (stx-get-balance tx-sender)))
(share (/ total-balance u4))
)
(try! (as-contract (stx-transfer? share tx-sender 'ST1J4G6RR643BCG8G8SR6M2D9Z9KXT2NJDRK3FBTK)))
(try! (as-contract (stx-transfer? share tx-sender 'ST20ATRN26N9P05V2F1RHFRV24X8C8M3W54E427B2)))
(try! (as-contract (stx-transfer? share tx-sender 'ST21HMSJATHZ888PD0S0SSTWP4J61TCRJYEVQ0STB)))
(try! (as-contract (stx-transfer? (stx-get-balance tx-sender) tx-sender 'ST2QXSK64YQX3CQPC530K79XWQ98XFAM9W3XKEH3N)))
(ok true)
)
)
)
可以看出。保护 claim 函数是不必要的,因为接收人是硬编码的。如果任何一个代币传输失败,则整个调用将会被恢复。 (想知道如何减少上面看到的重复代码吗?您可以在最佳实践的章节中找到一些提示和技巧。)
单元测试
smart-claimant 并不关心时间锁钱包会出错的原因。因此,我们只需要考虑一个成功转账的状态。
Clarinet.test({
name: "Disburses tokens once it can claim the time-locked wallet balance",
async fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const beneficiary = `${deployer.address}.smart-claimant`;
const wallet1 = accounts.get('wallet_1')!;
const wallet2 = accounts.get('wallet_2')!;
const wallet3 = accounts.get('wallet_3')!;
const wallet4 = accounts.get('wallet_4')!;
const unlock_height = 10;
const amount = 1000; // be sure to pick a test amount that is divisible by 4 for this test.
const share = Math.floor(amount / 4);
chain.mineBlock([
Tx.contractCall('timelocked-wallet', 'lock', [types.principal(beneficiary), types.uint(unlock_height), types.uint(amount)], deployer.address)
]);
chain.mineEmptyBlockUntil(unlock_height);
const block = chain.mineBlock([
Tx.contractCall('smart-claimant', 'claim', [], deployer.address)
]);
// Take the first receipt.
const [receipt] = block.receipts;
// The claim should be successful.
receipt.result.expectOk().expectBool(true);
// All wallets should have received their share.
receipt.events.expectSTXTransferEvent(share, beneficiary, wallet1.address);
receipt.events.expectSTXTransferEvent(share, beneficiary, wallet2.address);
receipt.events.expectSTXTransferEvent(share, beneficiary, wallet3.address);
receipt.events.expectSTXTransferEvent(share, beneficiary, wallet4.address);
}
});
该项目的完整源代码可以在这里找到: https://github.com/clarity-lang/book/tree/main/projects/timelocked-wallet