Cadence Anti-Patterns

[Previous << Design Patterns](https://qiita.com/flowcadence/items/bc0dad9850d2d6eaf7d2)
[Next >> Development Standards](https://qiita.com/flowcadence/items/d3c85d8bdec700424c5b)

https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#cadenceの言語仕様を学ぶ

<br>
これは、本番環境向けのCadenceコードで発見された場合、改善できる問題の意見の多いリストです。

<h1 id="Avoid-using-fully-authorized-account-references-as-a-function-parameter">Avoid using fully authorized account references as a function parameter</h1>

<h3>Problem</h3>

開発者は、ユーザーのアカウント参照またはアドレスを使用して、ユーザーの認証や操作を行うことができます。これを行うには、認証済みアカウント参照型を持つ関数にパラメータを追加します（`auth(...) &Account`）。認証済みアカウント参照は、トランザクションに署名することでのみ取得できるためです。

これが完全に認証されたアカウント参照である場合、これは問題となります。なぜなら、完全に認証されたアカウント参照は、アカウント上のいくつかの機密性の高い操作、例えばストレージへの書き込みなどへのアクセスを許可するからです。これは、悪意のあるユーザーに悪用される可能性があることを意味します。

<h3>Example:</h3>

```ruby
...
/* BAD CODE
   DO NOT COPY */

/* Imagine this code is in a contract that uses a `auth(Storage) &Account` parameter
 * to authenticate users to transfer NFTs */

/* They could deploy the contract with an Ethereum-style access control list */ functionality

access(all)
fun transferNFT(id: UInt64, owner: auth(Storage) &Account) {
    assert(owner(id) == owner.address)

    transfer(id)
}

/* But they could upgrade the function to have the same signature
 * so it looks like it is doing the same thing, but they could also drain a little bit
 * of FLOW from the user's vault, a totally separate piece of the account that
 * should not be accessible in this function
 * BAD */

access(all)
fun transferNFT(id: UInt64, owner: auth(Storage) &Account) {
    assert(owner(id) == owner.address)

    transfer(id)

    /* Sneakily borrow a reference to the user's Flow Token Vault
     * and withdraw a bit of FLOW
     * BAD */
    let vaultRef = owner.borrow<&FlowToken.Vault>(/storage/flowTokenVault)!
    let stolenTokens <- vaultRef.withdraw(amount: 0.1)

    /* deposit the stolen funds in the contract owners vault
       BAD */
    contractVault.deposit(from: <-stolenTokens)
}
...
```
<h3>Solution</h3>
プロジェクトは、リソースやCapabilityを認証オブジェクトとして使用するなど、ユーザーを認証する他の方法を見つけるべきです。また、ストレージやリンク操作のほとんどは、コントラクトユーティリティ関数内ではなく、トランザクション本体内で実行することを想定すべきです。

コールドストレージのマルチシグなど、認証済みアカウント参照（`auth(...) &Account`）を使用する必要があるシナリオもありますが、そのようなケースはまれであり、必要なアカウントの機能(account functionality)が非常に制限されたサブセットである場合にのみ使用すべきです。

<h1 id="Public-functions-and-fields-should-be-avoided">Public functions and fields should be avoided</h1>

<h3>Problem</h3>
コードを構築する際には、アクセス修飾子を常に把握し、公開すべきもののみを公開するようにしてください。誤って公開されたフィールドは、セキュリティ上の脆弱性となり得ます。
<h3>Solution</h3>

スマートコントラクトを記述する際には、すべてのフィールドと関数を確認し、[Entitlement](https://qiita.com/flowcadence/items/572df270135946f5290e#entitlements)（`access(E)`）によるアクセスが必要であることを確認するか、または、必要でない限り、`access(self)`、`access(contract)`、または`access(account)`などのnon-public [アクセス修飾子](https://qiita.com/flowcadence/items/572df270135946f5290e)を使用してください。

<h1 id="Capability-Typed-public-fields-are-a-security-hole">Capability-Typed public fields are a security hole</h1>
これは、上記の「パブリック関数およびフィールドは避けるべき」の特別なケースです。
<br>
<h3>Problem</h3>
パブリックフィールドの値はコピーすることができます。 Capabilityは値型なので、パブリックフィールドとして使用されている場合、誰でもフィールドからコピーして、そのCapabilityが公開している関数を呼び出すことができます。 コントラクトまたはリソースのフィールドとしてこの方法でCapabilityが保存されている場合、これはおそらく望む結果ではないでしょう。
<h3>Solution</h3>
Capabilityへのパブリックアクセスを可能にするには、この期待結果が明確になるよう、アカウントのパブリックエリアに配置します。

<h1 id="Public-admin-resource-creation-functions-are-unsafe">Public admin resource creation functions are unsafe</h1>

これは、上記の「パブリック関数およびフィールドは避けるべき」の特別なケースです。

<h3>Problem</h3>
リソースを作成するスマートコントラクト上のパブリック関数は、どのアカウントでも呼び出すことができます。そのリソースが管理機能へのアクセスを提供している場合、作成関数はパブリックにすべきではありません。
<h3>Solution</h3>
これを修正するには、スマートコントラクトの初期化時にそのリソースの単一インスタンスを作成し、必要に応じて、新しい作成関数を管理リソース内に含めることができます。
<h3>Example</h3>

```ruby
/* 疑似コード */

/* BAD */
access(all)
contract Currency {

    access(all)
    resource Admin {

        access(all)
        fun mintTokens()
    }

    /* Anyone in the network can call this function
       And use the Admin resource to mint tokens */
    access(all)
    fun createAdmin(): @Admin {
        return <-create Admin()
    }
}

/* This contract makes the admin creation private and in the initializer
 * so that only the one who controls the account can mint tokens
 * GOOD */
access(all)
contract Currency {

    access(all)
    resource Admin {

        access(all)
        fun mintTokens()

        /* Only an admin can create new Admins */
        access(all)
        fun createAdmin(): @Admin {
            return <-create Admin()
        }
    }

    init() {
        /* Create a single admin resource */
        let firstAdmin <- create Admin()

        /* Store it in private account storage, so only the admin can use it */
        self.account.storage.save(<-firstAdmin, to: /storage/currencyAdmin)
    }
}
```

<h1 id="do-not-modify-smart-contract-state-or-emit-events-in-public-struct-initializers">Do not modify smart contract state or emit events in public struct initializers</h1>

これは、スマートコントラクトに一般公開されたアクセス可能な部分があることのリスクを示すもう一つの例です。

<h3>Problem</h3>
現在、Cadenceにおけるデータ構造の定義(原文: Data structure definitions)は、誰でも使用できるように「パブリック」として宣言しなければなりません。構造体にはリソースのような制限がないため、認証(authorization)手続きを経ることなく誰でも新しい構造体のインスタンスを作成することができます。
<h3>Solution</h3>
構造体の作成に関連する、状態を変更する操作はすべて、構造体のイニシャライザー(init関数)ではなくリソースに格納すべきである。
<h3>Example</h3>

これはかつてはNBA Top Shotスマートコントラクトのバグでした。これを例として説明します。以前は、新しい(NBAの)プレイが作成されると、[(NBAの)プレイのレコードが構造体で初期化され](https://github.com/dapperlabs/nba-smart-contracts/blob/55645478594858a6830e4ab095034068ef9753e9/contracts/TopShot.cdc#L155-L158)、プレイIDを追跡する数値がインクリメントされ、イベントが発行されていました。

```ruby
/* Simplified Code
 * BAD
 */
access(all)
contract TopShot {

    /* The Record that is used to track every unique play ID */
    access(all)
    var nextPlayID: UInt32

    access(all)
    struct Play {

        access(all)
        let playID: UInt32

        init() {

            self.playID = TopShot.nextPlayID

            /* Increment the ID so that it isn't used again */
            TopShot.nextPlayID = TopShot.nextPlayID + 1

            emit PlayCreated(id: self.playID, metadata: metadata)
        }
    }
}
```

これはリスクです。なぜなら、誰でも好きなだけ`Play`構造体を生成することができ、`nextPlayID`フィールドが最大`UInt32`値まで増加し、事実上、新しいプレイが作成できなくなる可能性があるからです。また、偽のイベントが発行されることにもなります。

このバグは、[代わりにプレイを作成する管理機能(admin function)でコントラクトのstateを更新する](https://github.com/dapperlabs/nba-smart-contracts/blob/master/contracts/TopShot.cdc#L682-L685)ことで修正されました。

```ruby
/* Update contract state in admin resource functions
 * GOOD
 */
access(all)
contract TopShot {

    /* The Record that is used to track every unique play ID */
    access(all)
    var nextPlayID: UInt32

    access(all)
    struct Play {

        access(all)
        let playID: UInt32

        init() {
            self.playID = TopShot.nextPlayID
        }
    }

    access(all)
    resource Admin {

        /* Protected within the private admin resource */
        access(all)
        fun createPlay() {
            /* Create the new Play */
            var newPlay = Play()

            /* Increment the ID so that it isn't used again */
            TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1)

            emit PlayCreated(id: newPlay.playID, metadata: metadata)

            /* Store it in the contract storage */
            TopShot.playDatas[newPlay.playID] = newPlay
        }
    }
}
```

翻訳元

https://cadence-lang.org/docs/anti-patterns

***
<b>[Previous << Design Patterns](https://qiita.com/flowcadence/items/bc0dad9850d2d6eaf7d2)
</b>

[Flow blockchain / Cadence version1.0ドキュメント](https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a) (Cadence Anti-Patterns)

<b>[Next >> Development Standards](https://qiita.com/flowcadence/items/d3c85d8bdec700424c5b)</b>