Waves - スマートコントラクトでトークンに制約を加える

内容が古い可能性があります。
参考にする場合は、公式サイト、ドキュメントも合わせて確認してください。
このブログでの最新内容は、ページ末尾の「Waves」タグから辿ってください。

WavesPlatformのRIDEコントラクト(RIDE言語でのスマートコントラクト。勝手にそう呼んでる)には二種類ある。

  • アカウントに制約を加える AccountScript
  • トークンに制約を加える AssetScript

今回はAssetScriptの話。

AssetScript

特徴として、以前にも書いたが、
対象のトークンに対して、以下四つが行われた時、判定が行われる。

  • TransferTransaction
  • MassTransferTransaction
  • ReissueTransaction
  • BurnTransaction

また、これら以外の操作は実行できず、エラーとなる。
つまり、AssetScriptの付いたトークンはWavesDEXで交換(オーダーは出せるが、売買が成立しない)できなくなる。

AssetScriptは、アカウントによらずWaves全体に影響を与えるものである。
加えて、AccountScriptの後に判定が実施されるので、
コントラクトは署名やproofの判定をするものではなく、
Transaction当事者の外部から制限をかける形のものとなる。
(IDEのnotaryサンプルがそうなっている)

RIDEコントラクト例

いくつかコントラクトを書いてみる

例1

true

全てを許可する。
そして、WavesDEXで取引できなくなる。
取引してほしくない場合にこれをする。けれどもアトミックスワップで交換は可能である。

例2

let issuerPk = base58'<トークン発行者の公開鍵>'
tx.senderPk == issuerPk

発行者だけがトークンを操作できる。それ以外の人は何もできない。
発行者は一方的にトークンを送りつけることができる(エアドロップ)が、
受け取った側は、売れない、譲れない、焼却(バーン)できない、手放せない、嫌なトークンとなる。
スパムまっしぐらなので、たぶんやってはいけないコントラクト

例3

let issuerPk = base58'<トークン発行者の公開鍵>'
(tx.senderPk == issuerPk) || match tx {
 case tx: TransferTransaction =>
  addressFromRecipient(tx.recipient).bytes == addressFromPublicKey(issuerPk).bytes
 case _ => false
}

例2を修正し、発行者には送り返すことができるようになった。
投票、チケット、会員とかに使えそうなやつ。
Waves Auth APIや、モバイルウォレットのアドレスQRで他サービスと連携しやすい(と思いたい)。
発行者 対 利用者の基本パターンになりそうなコントラクト。

例4

let issuerPk = base58'<トークン発行者の公開鍵>'
(tx.senderPk == issuerPk) || match tx {
 case tx: TransferTransaction =>
  let issuerAddress = addressFromPublicKey(issuerPk)
  let senderPkBase58String = toBase58String(tx.senderPk)
  let agreement = getBoolean(issuerAddress, senderPkBase58String)
  if(isDefined(agreement)) then extract(agreement) else false
 case _ => false
}

発行者が許可した人宛てならトークンを送っても良い。
許可はデータトランザクションで与える。
トークン発行者宛ての許可をもちろん付与できるので、
データトランザクション分の手数料が余分にかかるが、例3より柔軟。

例5

let king = extract(addressFromString("kingAddress"))
let company = extract(addressFromString("companyAddress"))
let notary1 = addressFromPublicKey(extract(getByteArray(king,"notary1PK")))
let txIdBase58String = toBase58String(tx.id)
let notary1Agreement = getBoolean(notary1,txIdBase58String)
let isNotary1Agreed = if(isDefined(notary1Agreement)) then extract(notary1Agreement) else false
match tx { 
  case tx:TransferTransaction =>
    let recipientAddress = addressFromRecipient(tx.recipient)
    let recipientAgreement = getBoolean(recipientAddress,txIdBase58String)
    let isRecipientAgreed = if(isDefined(recipientAgreement)) then extract(recipientAgreement) else false
    let senderAddress = addressFromPublicKey(tx.senderPk)
    senderAddress.bytes == company.bytes || (isNotary1Agreed && isRecipientAgreed)
  case _ => false
}

IDEのnotaryサンプル。
トークン発行者とは別の人がトークン保有に権限を持ち管理する。
また、その管理者を指名するさらに別の役割がある。
そして受け取る方も表明する必要がある。
権力の証明の元、お互いに合意したトークン受け渡しを行う。っぽい。

AssetScript付きトークンを発行する

IssueTransactionV2を使って行う。
(SetScriptTransactionはAccountScript用)
Issueでしか行えないので、一度発行したら修正不可。
APIを直接叩いてもよいが、今回はコードを書く

WavesJ (Java用SDK)

WavesのSDKは、いくつかの言語用に用意されているが、
現在IssueTransactionV2に対応しているのはJava用のWavesJだけなのでこれを使ってKotlinでやる

コード

WavesJとKotlinを使える様にbuild.Gradleを用意する

buildscript {
    ext.kotlin_version = '1.2.51'
    ext.wavesj_version = '0.9'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

group 'com.dekirukigasuru'
version '1.0-SNAPSHOT'

apply plugin: 'application'
apply plugin: 'kotlin'

mainClassName = 'MainKt'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "com.wavesplatform:wavesj:$wavesj_version"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

src/main/kotlin/Main.kt を書く

import com.wavesplatform.wavesj.*

fun main(args: Array<String>) {
    val chainID = 'Z'.toByte()
    val issuerAccount = PrivateKeyAccount.fromSeed("waves", 0, chainID)
    val url = "http://localhost:6869"

    val node = Node(url)

    // base64 encoded script
    val script = "AQa3b8tH"
    // token issue
    val assetID = node.issueAsset(
            issuerAccount,
            chainID,
            "Test Token",
            "this is test",
            100,
            0,
            false,
            script,
            Asset.TOKEN
    )

    println(assetID)
}

気にするところ。

  • chainID と issuerAccountのシード および url は自分のテスト環境用の値にする
  • トークン発行メソッドは、Base64エンコードされたスクリプトを要求するので、その形式で与える。これはIDEからコピーできる
  • あとで必要になるので、アセットIDを控えておく

アセットID用意できたら他の言語でやれば良い

実際に試すにあたって

今回みたいなことは、プライベートチェーン上でやると色々と楽。
建て方はドキュメント参照(リンク)。

わたしの設定ファイル


See also