- Go back to TOP(インデックスページへ)
- Introduction
- Validation Goals
- Updating a Contract
- Fields
- Structs, Resources and Interfaces
- Enums
- Functions
- Events
- Constructors
- Imports
- #removedType Pragma
- RLP
Introduction
スマートコントラクトは、アカウントのコントラクトストレージ領域に保存されるデータ(その状態(state))とコード(その機能(functions))の集合体です。スマートコントラクトが更新される際には、すでに保存されているデータに対して実行時に不整合が生じないように、変更が導入されることを確認することが重要です。Cadenceでは、更新前にスマートコントラクトとそのすべてのコンポーネントを検証することで、この状態の一貫性を維持しています。
Validation Goals
スマートコントラクトの更新の検証により、以下のことが保証されます。
- スマートコントラクトが更新された際に、保存されたデータの意味が変わらないこと。
- 保存されたデータのデコードや使用により、実行時にクラッシュが発生しないこと。
- 例えば、既存の保存されたデータに新しいフィールドが存在しないため、フィールドを追加することは無効です。
- 既存のデータをロードすると、そのようなフィールドには無意味な値または欠落した値が返されます。
- フィールドへのアクセスを静的にチェックすることは有効ですが、フィールドに欠落値や無意味な値があるため、フィールドにアクセスするとインタプリタがクラッシュします。
しかし、次のことは保証されません。
- 更新されたスマートコントラクトをインポートするすべてのプログラムが有効な状態を維持する。
例:- 更新されたスマートコントラクトは、関数のシグネチャを変更したりすることがあります。
- すると、そのフィールドや関数を使用するすべてのプログラムで意味論的エラーが発生します。
Updating a Contract
スマートコントラクトの変更は、新しいスマートコントラクトの追加、既存のスマートコントラクトの削除、または既存のスマートコントラクトの更新によって導入することができます。ただし、これらの変更の一部は、前述のとおり、データの不整合につながる可能性があります。
有効な変更
- 新しいcontractの追加は有効です。
- enumの宣言を持たないcontract/contract-interfaceの削除は有効です。
- contractの更新は、以下のセクションで説明する制限事項に従う限り有効です。
無効な変更
- enum宣言を含むcontract/contract-interfaceを削除することはできません。
- contractを削除すると、同じ名前の新しいcontractを追加できるようになります。
- 新しいcontractには、古いcontractと同じ名前のenum宣言を追加できますが、構造は異なります。
- これにより、すでに格納されているenum型の値の意味が変更される可能性があります。
contractはフィールドや複合型、関数、コンストラクタなどの他の宣言から構成されます。既存のcontractが更新されると、その内部宣言もすべて検証されます。
Contract Fields
スマートコントラクトがデプロイされると、そのcontractのフィールドはアカウントのcontractストレージに保存されます。contractのフィールドを変更すると、プログラムがデータを処理する方法のみが変更され、すでに保存されているデータ自体は変更されません。これは、前項で述べたように、実行時に不整合が生じる可能性につながります。
フィールドに対して実行可能な更新と、contractのフィールドを変更する際に課される制限については、以下のFieldsセクションを参照してください。
Nested Declarations
スマートコントラクトには、構造体、リソース、インターフェース、列挙型(enum)などのネストされた複合型宣言を含めることができます。contractが更新されると、そのネストされた宣言がチェックされます。その理由は以下の通りです。
- それらは、同じcontractのフィールドの型注釈(type annotation)として、直接または間接的に使用できるからです。
- サードパーティのスマートコントラクトは、このcontractで定義された型をインポートし、型注釈(type annotation)として使用することができます。
- したがって、型定義を変更することは、そのようなフィールドの型注釈(type annotation)を変更することと同じです(これもまた、後述のFieldsのセクションで説明するように、無効です)。
ネストされた宣言に対して行える変更と、その更新に関する制限事項については、以下のセクションで説明します。
- Structs, resources and interface
- Enums
- Functions
- Events
- Constructors
Fields
フィールドは、コントラクト、構造、リソース、またはインターフェースに属する場合があります。
有効な変更:
フィールドの削除は有効です。
// Existing contract
access(all)
contract Foo {
access(all)
var a: String
access(all)
var b: Int
}
// Updated contract
access(all)
contract Foo {
access(all)
var a: String
}
- 削除されたフィールドのデータは、ストレージではアクセスできなくなるため、未使用のままとなります。
- ただし、実行時にクラッシュが発生することはありません。
フィールドの順序を変更することは有効です。
// Existing contract
access(all)
contract Foo {
access(all)
var a: String
access(all)
var b: Int
}
// Updated contract
access(all)
contract Foo {
access(all)
var b: Int
access(all)
var a: String
}
フィールドのアクセス修飾子の変更は有効です。
// Existing contract
access(all)
contract Foo {
access(all)
var a: String
}
// Updated contract
access(all)
contract Foo {
access(self)
var a: String // access modifier changed to 'access(self)'
}
無効な変更:
新しいフィールドを追加することはできません。
// Existing contract
access(all)
contract Foo {
access(all)
var a: String
}
// Updated contract
access(all)
contract Foo {
access(all)
var a: String
access(all)
var b: Int // Invalid new field
}
- イニシャライザー(init関数)は、スマートコントラクトが初めてデプロイされたときに一度だけ実行されます。スマートコントラクトが更新された場合、再実行されることはありません。しかし、型チェックを満たすためには、更新されたcontractにもイニシャライザーが必要です。
- したがって、新たに追加されたフィールドの初期化は実行されないため、保存されたデータには新しいフィールドは含まれません。
- 保存されたデータをデコードすると、そのようなフィールドには無意味な文字列や欠落した値が含まれます。
既存のフィールドの型変更は無効です。
// Existing contract
access(all)
contract Foo {
access(all)
var a: String
}
// Updated contract
access(all)
contract Foo {
access(all)
var a: Int // Invalid type change
}
- すでに保存されているcontractでは、フィールドaの値はString型となります。
- フィールドaの型をIntに変更すると、実行時にすでに保存されているStringの値がIntとして読み込まれることになり、その結果、デシリアライゼーションエラーが発生します。
- フィールドの型を既存の型のサブタイプ/スーパー型に変更することも、デコード/エンコード時に問題を引き起こす可能性があるため、有効ではありません。
- 例:Int64フィールドをInt8に変更 - 格納されたフィールドの数値が624になる可能性があり、これはInt8の値域を超える。
- しかし、これは現在の実装の制限であり、将来のバージョンのCadenceでは、既存のフィールドを移行する手段を提供することで、フィールドのタイプをサブタイプに変更できるようになる可能性があります。
Structs Resources and Interfaces
有効な変更:
- 新しい構造体、リソース、インターフェースを追加することは有効です。
- 構造体/リソースにインターフェース適合(interface conformance)を追加することは有効です。格納データは具体的な型/値のみを格納し、適合情報(conformance info)は格納しないためです。
// Existing struct
access(all)
struct Foo {
}
// Upated struct
access(all)
struct Foo: T {
}
- しかし、準拠(conformance)を追加する際に既存の構造の変更も必要となる場合(例えば、新しい準拠によって強制される新しいフィールドを追加する場合など)、その他の制限(Fieldsによる制限など)により、そのような更新が実行できない場合があります。
無効な変更:
- 既存の宣言を削除するには、#removedType プラグマを使用する必要があります。
- 宣言を削除すると、同じ名前で構造が異なる新しい宣言を追加することができます。
- その宣言を使用するプログラムでは、格納データに不整合が生じます。
- 宣言の名前を変更しても無効です。これは、既存の宣言を削除して新しい宣言を追加するのと同じ効果があります。
- 宣言の型を変更することはできません。つまり、structからinterfaceに変更したり、その逆を行ったりすることはできません。
// Existing struct
access(all)
struct Foo {
}
// Changed to a struct interface
access(all)
struct interface Foo { // Invalid type declaration change
}
- struct/resourceのインターフェース適合性(interface conformance)を削除することは無効です。
// Existing struct
access(all)
struct Foo: T {
}
// Upated struct
access(all)
struct Foo {
}
Updating Members
複合宣言(構造体、リソース、インターフェース)も、contractと同様に、フィールドやその他のネストされた宣言をそのメンバーとして持つことができます。このような複合宣言を更新する場合、そのメンバーすべてを更新する必要があります。
以下では、構造体、リソース、インターフェースのメンバーを更新する場合の制限事項について説明します。
- Fields
- Nested structs, resources and interfaces
- Enums
- Functions
- Constructors
Enums
有効な変更:
- 新しいenumの宣言を追加することは有効です。
無効な変更:
- 既存のenum宣言を削除することは無効です。
- それ以外の場合、既存のenumを削除し、同じ名前で構造が異なる新しいenum宣言を追加することは可能です。
- 新しい構造は、互換性のない変更(型や列挙ケースの変更など)が加えられる可能性があります。
- 名前を変更することは、既存のenumを削除し、新しいenumを追加することと同等であるため、無効です。
- 生の型(raw type)を変更することは無効です。
// Existing enum with `Int` raw type
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum with `UInt8` raw type
access(all)
enum Color: UInt8 { // Invalid change of raw type
access(all)
case RED
access(all)
case BLUE
}
- 列挙値(enum value)が保存される際には、列挙値に関連付けられた生の値(raw value)が保存されます。
- 型が変更された場合、すでに保存されている値が更新された型と同じ値空間内にない場合には、デシリアライズに失敗する可能性があります。
Updating Enum Cases
Enumは列挙型宣言から構成され、Enumの更新にはenum-caseの変更も含まれる場合があります。enum-caseは、Cadenceインタープリタおよびランタイムで生の値を使用して表されます。したがって、enum-caseのraw valueを変更する変更は許可されません。そうでないと、変更された生の値によって、すでに格納されているEnumの値が、元の値とは異なる意味を持つ可能性があります(型の混乱)。
有効な変更:
- 既存のenum-caseの最後にenum-caseを追加することは有効です。
// Existing enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
access(all)
case GREEN // valid new enum-case at the bottom
}
無効な変更
- 既存のEnumの先頭または途中にenum-caseを追加することは無効です。
// Existing enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case GREEN // invalid new enum-case in the middle
access(all)
case BLUE
}
- enum-caseの名前を変更することはできません。
// Existing enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case GREEN // invalid change of names
}
- Color.BLUE の以前に保存された生の値は、現在では Color.GREEN を表します。すなわち、保存された値は意味が変更されたため、有効な変更ではありません。
- 同様に、古い名前BLUEの新しいenum-caseを追加することも可能で、これは新しい生の値を取得します。 その後、同じenum-caseのColor.BLUEが実行時に変更の前後で2つの生の値を使用することになり、これも無効です。
enum-caseの削除は無効です。削除すると、enum-caseの追加と削除が可能になり、これは名前の変更と同じ効果があります。
// Existing enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum
access(all)
enum Color: Int {
access(all)
case RED
// invalid removal of `case BLUE`
}
enum-caseの順序を変更することは許可されてません。
// Existing enum
access(all)
enum Color: Int {
access(all)
case RED
access(all)
case BLUE
}
// Updated enum
access(all)
enum Color: UInt8 {
access(all)
case BLUE // invalid change of order
access(all)
case RED
}
- Enumの生の値(raw value)は暗黙的であり、定義された順序に対応します。
- enum-caseの順序を変更すると、生の値(raw value)を変更したのと同じ効果があり、前述のとおり、ストレージの不整合や型混乱を引き起こす可能性があります。
Functions
function定義の追加、変更、削除は常に有効です。function定義はデータとして保存されることはないためです(funtion定義はコードの一部であり、データではありません)。
- 関数の追加は有効です。
- 関数の削除は有効です。
- 関数のシグネチャ(パラメータ、戻り値の型)の変更は有効です。
- 関数本体の変更は有効です。
- アクセス修飾子の変更は有効です。
ただし、 function type の変更は、それが使用される場所によって有効または無効となる場合があります。function typeが複合型フィールド(直接または間接)の型注釈で使用されている場合、function typeのシグネチャを変更することは、そのフィールドの型注釈を変更することと同じであり(無効です)。
Events
イベントはチェーンに保存されません。イベントに変更を加えても、保存されたデータには影響しません。したがって、スマートコントラクトにおけるイベントの追加、削除、変更は有効です。
Constructors
functionと同様に、コンストラクタも保存されません。したがって、コンストラクタへの変更はすべて有効です。
Imports
contractは、他のプログラムから宣言(型、関数、変数など)をインポートすることができます。これらのインポートされたプログラムは、デプロイ時にすでに検証されています。したがって、インポートされるたびに宣言を検証する必要はありません。
removedType Pragma
通常、複合型またはインターフェースに関わらず、型宣言を削除することはできません。ただし、複合型の宣言を「廃止」してスマートコントラクトから削除し、同じ名前の宣言が再び追加されることを防ぐ必要がある場合には、特別なプラグマを使用することができます。このプラグマはインターフェースでは使用できません。
このプラグマを使用するには、Tという型を削除したい場合、Tの宣言と同じスコープに#removedType(T)行をcontractに追加するだけです。例えば、次のように定義されたリソース定義Rを削除するには:
access(all) contract Foo {
access(all) resource R {
// definition of R ...
}
// other stuff ...
}
contractを以下のように変更する:
access(all) contract Foo {
#removedType(R)
// other stuff ...
}
これにより、Rという名前の型がFooでネストした宣言として再び宣言されることが防止され、型を削除することによって通常発生するセキュリティ上の問題が回避されます。具体的には、#removedType(T)プラグマがスマートコントラクトのあるスコープレベルに存在する場合、Tという名前の新しい型はそのスコープに追加できません。さらに、いったん追加された#removedTypeプラグマは、上記の制限を回避できるため、決して削除できません。
このプラグマの動作は必ずしも最終的なものではなく、変更される可能性があることにご注意ください。
翻訳元->https://cadence-lang.org/docs/language/contract-updatability