2021-07-30 19:15 | 出处: FLOW福洛链
本文由Flow的技术大使FOU编写。他在学习Flow链上稳定币FUSD的过程中总结得出,希望能够给学习FUSD代码的小伙伴一定的启发和帮助。
摘要
1. FUSD基础源码分析
2. 重点分析FUSD合约是如何授予别人mint权限并能单方面撤回的
3. 补足FUSD Github中缺失的transaction
4. 其他想法
建议前置知识:
1. 了解Fungible Token 标准: [https://github.com/onflow/flow-ft]
2. 熟悉Capability: [https://docs.onflow.org/cadence/language/capability-based-access-control/]
Flow的相关链接:
FUSD官方信息:
[https://docs.onflow.org/fusd/]
[https://docs.onflow.org/fusd/transactions/]
[https://docs.onflow.org/fusd/providers/]
FUSD在测试网和主网上的合约地址:
https://flow-view-source.com/testnet/account/0 xe223d8a629e49c68/contract/FUSD
FUSD Github:
[https://github.com/onflow/fusd]
查询FUSD余额:
[https://github.com/onflow/fusd/blob/main/transactions/scripts/get_fusd_balance.cdc]
FUSD在Flow Scan:
[https://flowscan.org/contract/A.3c5959b568896393.FUSD/transfers]
FUSD合约概览:
FUSD合约遵循Fungible Token标准,因此任何人都可以创建 `Vault Resource`,并通过 `Vault Resource`接收或转出FUSD,并且任何人都可以查询所有人的FUSD 余额(Balance)。
此外,合约的AdminAccount可以通过“授予”其他用户 `Minter Capability`以允许其他账户mint FUSD。并且,AdminAccount可以单方面通过 `unlink capability`撤回(revoke)用户的mint FUSD的权利。
实现细节:
合约的前半部分是主要是实现Fungible Token标准的interface,在此不做赘述。将来我们会详细分析FT和NFT标准合约。
FUSD合约的亮点是在对于Minter权限的处理上。
首先我们可以看到在 `init()`语句中,创建了 `Administrator`的 `resource`并存储在 `adminAccount`的 `storage`中。
```swiftlet admin <- create Administrator()adminAccount.save(<-admin, to: self.AdminStoragePath)```
`Administrator` `resource`只有一个用来创建新minter的 `function`,这个function会创建并返回一个 `Minter` `resource`
```swiftpub resource Administrator {// createNewMinter// Function that creates a Minter resource.// This should be stored at a unique path in storage then a capability to it wrapped// in a MinterProxy to be stored in a minter account‘s storage.// This is done by the minter account running:// transactions/FUSD/minter/setup_minter_account.cdc// then the admin account running:// transactions/flowArcaddeToken/admin/deposit_minter_capability.cdc//pub fun createNewMinter(): @Minter {emit MinterCreated()return <- create Minter()}} ;
`Minter` `resource`包含对应的 `mintTokens function`可以铸造新的FUSD,并将新铸造的FUSD数量累加到总供应量( `totalSupply`)中。mintTokens function利用 `pre condition`确保amount变量为正数,并创建返回一个 `FUSD.Vault resource`。
```swift// Minter// Resource object that can mint new tokens.// The admin stores this and passes it to the minter account as a capability wrapper resource.//pub resource Minter {// mintTokens// Function that mints new tokens, adds them to the total supply,// and returns them to the calling context.//pub fun mintTokens(amount: UFix**): @FUSD.Vault {pre {amount > 0.0: "Amount minted must be greater than zero"}FUSD.totalSupply = FUSD.totalSupply + amountemit TokensMinted(amount: amount)return <-create Vault(balance: amount)}}```
通过上述代码, `AdminAccount`可以自己通过创建 `Minter Resource`铸造新的FUSD。
不过,正如我们可以看到FUSD合约中并没有为 `AdminAccount`创建Vault,因此我们需要单独用另外一个transaction创建对应的FUSD的Vault,同时如果用其他账户做测试的话,也需要对应的其他账户发送这个transaction用以创建Vault。
```bashimport FungibleToken from 0 xADDRESSimport FUSD from 0 xADDRESStransaction {prepare(signer: AuthAccount) {// It‘s OK if the account already has a Vault, but we don‘t want to replace itif(signer.borrow<&FUSD.Vault>(from: /storage/fusdVault) != nil) {return}// Create a new FUSD Vault and put it in storagesigner.save(<-FUSD.createEmptyVault(), to: /storage/fusdVault)// Create a public capability to the Vault that only exposes// the deposit function through the Receiver interfacesigner.link<&FUSD.Vault{FungibleToken.Receiver}>(/public/fusdReceiver,target: /storage/fusdVault)// Create a public capability to the Vault that only exposes// the balance field through the Balance interfacesigner.link<&FUSD.Vault{FungibleToken.Balance}>(/public/fusdBalance,target: /storage/fusdVault)}}```
接着我们可以用 `AdminAccount`创建Minter Resource以铸造新的FUSD,范例transaction如下。
此范例中,创建的minter resource最后是被destroy了。但也可以把这个resource存储在AdminAccount中,不需要destroy。
```swiftimport FungibleToken from 0 xADDRESSimport FUSD from 0 xADDRESStransaction(recipientAddress: Address, amount: UFix**) {let tokenReceiver: &{FungibleToken.Receiver}prepare(adminAccount: AuthAccount) {self.tokenReceiver = getAccount(recipientAddress).getCapability(/public/fusdReceiver)!.borrow<&{FungibleToken.Receiver}>()?? panic("Unable to borrow receiver reference")// Create a reference to the admin resource in storage.let tokenAdmin = adminAccount.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath)?? panic("Could not borrow a reference to the admin resource")// Create a new minter resource and a private link to a capability for it in the admin‘s storage.let minter <- tokenAdmin.createNewMinter()let mintedVault <- minter.mintTokens(amount: amount)self.tokenReceiver.deposit(from: <-mintedVault)destroy minter}}```
flow cli 范例:
```bashflow transactions send ./transactions/fusd_direct_mint.cdc --signer ADMIN_ACCOUNT --arg Address:0 x01cf0e2f2f715450 --arg UFix**:10.0```
是不是也能通过多签(multi-signer)把这个minter resource直接授予别人?还没测试过。
除此之外, `AdminAccount`还可以通过 `capability`授予其他账户同样mint FUSD的权限。
大致步骤如下:
1.新Minter账号调用FUSD合约中的 `createMinterProxy()` function创建 `MinterProxy resource` 并存储在自己的 `storage`中,然后再把存储有 `MinterProxy resource`的 `storage` link 到 `public` 以便 `AdminAccount`调用
2. `AdminAccount`调用自己的 `Administrator` resource创建 `Minter resource`并存储到`storage`中,然后link到 `private` 以创建 `minterCapability` AdminAccount通过新Minter账号中的 `public` `MinterProxy resource`,调用MinterProxy resource中 `setMinterCapability()`,授予新Minter账户铸造FUSD的权利
3. 新Minter账户通过调用自己 `MinterProxy resource`中的mintTokens() function铸造新的FUSD
FUSD合约相关代码如下:
```swiftpub resource interface MinterProxyPublic {pub fun setMinterCapability(cap: Capability<&Minter>)}// MinterProxy//// Resource object holding a capability that can be used to mint new tokens.// The resource that this capability represents can be deleted by the admin// in order to unilaterally revoke minting capability if needed.pub resource MinterProxy: MinterProxyPublic {// access(self) so nobody else can copy the capability and use it.access(self) var minterCapability: Capability<&Minter>?// Anyone can call this, but only the admin can create Minter capabilities,// so the type system constrains this to being called by the admin.pub fun setMinterCapability(cap: Capability<&Minter>) {self.minterCapability = cap}pub fun mintTokens(amount: UFix**): @FUSD.Vault {return <- self.minterCapability!.borrow()!.mintTokens(amount:amount)}init() {self.minterCapability = nil}}// createMinterProxy//// Function that creates a MinterProxy.// Anyone can call this, but the MinterProxy cannot mint without a Minter capability,// and only the admin can provide that.//pub fun createMinterProxy(): @MinterProxy {return <- create MinterProxy()}```
下面依次是需要用到的transaction代码。
用新Minter账户创建MinterProxy
```swiftimport FUSD from 0 xADDRESStransaction {prepare(adminAccount: AuthAccount) {let minter_proxy <- FUSD.createMinterProxy()adminAccount.save(<- minter_proxy, to: FUSD.MinterProxyStoragePath)adminAccount.link<&FUSD.MinterProxy{FUSD.MinterProxyPublic}>(FUSD.MinterProxyPublicPath,target: FUSD.MinterProxyStoragePath) ?? panic("Could not link minter proxy")}}``````swiftflow transactions send ./transactions/fusd_initialize_minter.cdc --signer new_minter_account```
接下来这个transaction要由 `AdminAccount`发起,作用是创建 `Minter Resource`,存储并链接到 `AdminAccount`的 `private storage`中,最后再通过新Minter账户中的存储在public storage中的 `MinterProxy Resource`,调用 `setMinterCapability()`,将AdminAccount的capability链接进去。
```swiftimport FUSD from 0 xADDRESStransaction(newMinterAccount: Address) {let resourceStoragePath: StoragePathlet capabilityPrivatePath: CapabilityPathlet minterCapability: Capability<&FUSD.Minter>prepare(adminAccount: AuthAccount) {// These paths must be unique within the FUSD contract account‘s storageself.resourceStoragePath = /storage/minter_01self.capabilityPrivatePath = /private/minter_01// Create a reference to the admin resource in storage.let tokenAdmin = adminAccount.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath)?? panic("Could not borrow a reference to the admin resource")// Create a new minter resource and a private link to a capability for it in the admin‘s storage.