diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/BuildInfo.yaml b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/BuildInfo.yaml index 28e34cd3234a0..b0368d198af7f 100644 --- a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/BuildInfo.yaml +++ b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/BuildInfo.yaml @@ -175,7 +175,7 @@ compiled_package_info: ? address: "00000000000000000000000000000001" name: XUS : DiemFramework - source_digest: C5415C7DFBC3B6BC4AB994BFE0B6B8C580089AD7D664589FF329434BA328B3AD + source_digest: 8728DA1AAB23BC8D232C36BB5800CD53D16D26533F5B895203D6C5EAE22C4EEA build_flags: dev_mode: false test_mode: false diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiToken.mv b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiToken.mv index b19bdb21f3b04..5e550a09edc09 100644 Binary files a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiToken.mv and b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiToken.mv differ diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiTokenBalance.mv b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiTokenBalance.mv index 37cb55e8e832e..7a2c139b7d503 100644 Binary files a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiTokenBalance.mv and b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/bytecode_modules/MultiTokenBalance.mv differ diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiToken.md b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiToken.md index 79f4d8f9d0193..4017522e1db14 100644 --- a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiToken.md +++ b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiToken.md @@ -23,6 +23,7 @@ - [Function `split`](#0x1_MultiToken_split) - [Function `initialize_multi_token`](#0x1_MultiToken_initialize_multi_token) - [Function `create`](#0x1_MultiToken_create) +- [Module Specification](#@Module_Specification_1)
use 0x1::Errors;
@@ -451,6 +452,23 @@ Returns the supply of tokens with id
on the chain.
+
+
+
+Specification
+
+
+
+let addr = GUID::id_creator_address(id);
+let token_collection = get_tokens<TokenType>(addr);
+let min_token_idx = find_token_index_by_id(token_collection,id);
+aborts_if !exists<TokenDataCollection<TokenType>>(addr);
+aborts_if !is_in_tokens(token_collection, id);
+ensures result == token_collection[min_token_idx].supply;
+
+
+
+
@@ -483,6 +501,27 @@ Extract the MultiToken data of the given token into a hot potato wrapper.
+
+
+
+Specification
+
+
+
+let addr = GUID::id_creator_address(nft.id);
+let token_collection = get_tokens<TokenType>(addr);
+let id = nft.id;
+let min_token_idx = find_token_index_by_id(token_collection, id);
+aborts_if !exists<TokenDataCollection<TokenType>>(addr);
+aborts_if token_collection[min_token_idx].metadata == Option::spec_none();
+aborts_if !is_in_tokens(token_collection, id);
+ensures result == TokenDataWrapper { origin: addr, index: min_token_idx,
+metadata: Option::borrow(token_collection[min_token_idx].metadata)};
+ensures get_tokens<TokenType>(addr)[min_token_idx].metadata == Option::spec_none();
+
+
+
+
@@ -513,6 +552,24 @@ Restore the token in the wrapper back into global storage under original address
+
+
+
+Specification
+
+
+
+let addr = wrapper.origin;
+let token_collection = get_tokens<TokenType>(addr);
+let item_opt = token_collection[wrapper.index].metadata;
+aborts_if !exists<TokenDataCollection<TokenType>>(addr);
+aborts_if len(token_collection) <= wrapper.index;
+aborts_if item_opt != Option::spec_none();
+ensures get_tokens<TokenType>(addr)[wrapper.index].metadata == Option::spec_some(wrapper.metadata);
+
+
+
+
@@ -534,7 +591,11 @@ Finds the index of token with the given id in the gallery.
fun index_of_token<TokenType: store>(gallery: &vector<TokenData<TokenType>>, id: &GUID::ID): Option<u64> {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].token_id.id != id;
+ };(i < len)}) {
if (GUID::eq_id(&Vector::borrow(gallery, i).token_id, id)) {
return Option::some(i)
};
@@ -546,6 +607,21 @@ Finds the index of token with the given id in the gallery.
+
+
+
+Specification
+
+
+
+let min_token_idx = find_token_index_by_id(gallery, id);
+let post res_id = Option::borrow(result);
+ensures is_in_tokens(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ensures result == Option::spec_none() <==> !is_in_tokens(gallery, id);
+
+
+
+
@@ -574,6 +650,20 @@ Join two multi tokens and return a multi token with the combined value of the tw
+
+
+
+Specification
+
+
+
+aborts_if token.id != other.id with EWRONG_TOKEN_ID;
+aborts_if MAX_U64 - token.balance < other.balance with ETOKEN_BALANCE_OVERFLOWS;
+ensures token.balance == old(token).balance + other.balance;
+
+
+
+
@@ -606,6 +696,21 @@ Split the token into two tokens, one with balance amount
and the ot
+
+
+
+Specification
+
+
+
+aborts_if token.balance < amount with EAMOUNT_EXCEEDS_TOKEN_BALANCE;
+ensures result_1.balance == token.balance - amount;
+ensures result_2.balance == amount;
+ensures result_1.id == result_2.id;
+
+
+
+
@@ -634,6 +739,21 @@ Initialize this module, to be called in genesis.
+
+
+
+Specification
+
+
+
+let addr = Signer::address_of(account);
+aborts_if addr != ADMIN;
+aborts_if exists<Admin>(addr);
+ensures exists<Admin>(addr);
+
+
+
+
@@ -681,3 +801,59 @@ Create a TokenData<
+
+
+Specification
+
+
+
+let addr = Signer::address_of(account);
+let post post_tokens = get_tokens<TokenType>(addr);
+aborts_if !exists<Admin>(ADMIN);
+aborts_if exists<GUID::Generator>(addr) && global<GUID::Generator>(addr).counter + 1 > MAX_U64;
+ensures result.balance == amount;
+ensures GUID::id_creator_address(result.id) == addr;
+ensures exists<TokenDataCollection<TokenType>>(addr);
+ensures post_tokens[len(post_tokens) - 1] ==
+ TokenData<TokenType> {metadata: Option::spec_some(metadata), token_id: GUID::GUID {id: result.id}, content_uri, supply:amount};
+
+
+
+
+
+
+
+
+## Module Specification
+
+
+
+
+
+
+fun get_tokens<TokenType>(addr: address): vector<TokenData<TokenType>>{
+ global<TokenDataCollection<TokenType>>(addr).tokens
+}
+
+
+
+
+
+
+
+
+fun is_in_tokens<TokenType>(tokens: vector<TokenData<TokenType>>, token_id: GUID::ID): bool {
+ exists token in tokens: token.token_id.id == token_id
+}
+
+
+
+
+
+
+
+
+fun find_token_index_by_id<TokenType>(tokens: vector<TokenData<TokenType>>, id: GUID::ID): u64 {
+ choose min i in range(tokens) where tokens[i].token_id.id == id
+}
+
diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiTokenBalance.md b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiTokenBalance.md
index 588d86e0573be..aad95c08c3f53 100644
--- a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiTokenBalance.md
+++ b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/docs/MultiTokenBalance.md
@@ -52,6 +52,52 @@ Balance holding tokens of TokenType
as well as information of appro
+
+
+
+Specification
+
+
+
+invariant forall i1 in range(gallery), i2 in range(gallery) where gallery[i1].id == gallery[i2].id:
+i1 == i2;
+
+
+
+
+
+
+
+
+fun get_token_balance_gallery<TokenType>(addr: address): vector<Token<TokenType>>{
+ global<TokenBalance<TokenType>>(addr).gallery
+}
+
+
+
+
+
+
+
+
+fun is_in_gallery<TokenType>(gallery: vector<Token<TokenType>>, token_id: GUID::ID): bool {
+ exists i in range(gallery): gallery[i].id == token_id
+}
+
+
+
+
+
+
+
+
+fun find_token_index_by_id<TokenType>(gallery: vector<Token<TokenType>>, id: GUID::ID): u64 {
+ choose i in range(gallery) where gallery[i].id == id
+}
+
+
+
+
@@ -159,12 +205,35 @@ gallery, we combine it with the new one and make a token of greater value.
MultiToken::join<TokenType>(&mut token, original_token);
};
let gallery = &mut borrow_global_mut<TokenBalance<TokenType>>(owner).gallery;
- Vector::push_back(gallery, token)
+ Vector::push_back(gallery, token);
}
+
+
+
+Specification
+
+
+
+let gallery = get_token_balance_gallery<TokenType>(owner);
+let token_bal = token.balance;
+let min_token_idx = find_token_index_by_id(gallery, token.id);
+let balance = gallery[min_token_idx].balance;
+let post post_gallery = get_token_balance_gallery<TokenType>(owner);
+aborts_if !exists<TokenBalance<TokenType>>(owner);
+aborts_if is_in_gallery(gallery, token.id) && MAX_U64 - token.balance < balance;
+ensures is_in_gallery(gallery, token.id) ==> len(gallery) == len(post_gallery);
+ensures !is_in_gallery(gallery, token.id) ==> len(gallery) + 1 == len(post_gallery);
+ensures is_in_gallery(gallery, token.id) ==> post_gallery[len(gallery) - 1].balance ==
+ token_bal + gallery[min_token_idx].balance;
+ensures post_gallery[len(post_gallery) - 1].id == token.id;
+
+
+
+
@@ -195,6 +264,21 @@ Remove a token of certain id from the owner's gallery and return it.
+
+
+
+Specification
+
+
+
+let gallery = get_token_balance_gallery<TokenType>(owner);
+aborts_if !exists<TokenBalance<TokenType>>(owner);
+aborts_if !is_in_gallery(gallery, id);
+ensures !is_in_gallery(get_token_balance_gallery<TokenType>(owner), id);
+
+
+
+
@@ -216,7 +300,11 @@ Finds the index of token with the given id in the gallery.
fun index_of_token<TokenType: store>(gallery: &vector<Token<TokenType>>, id: &GUID::ID): Option<u64> {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].id != id;
+ };(i < len)}) {
if (MultiToken::id<TokenType>(Vector::borrow(gallery, i)) == *id) {
return Option::some(i)
};
@@ -228,6 +316,21 @@ Finds the index of token with the given id in the gallery.
+
+
+
+Specification
+
+
+
+let min_token_idx = choose min i in range(gallery) where gallery[i].id == id;
+let post res_id = Option::borrow(result);
+ensures is_in_gallery(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ensures result == Option::spec_none() <==> !is_in_gallery(gallery, id);
+
+
+
+
@@ -253,6 +356,19 @@ Returns whether the owner has a token with given id.
+
+
+
+Specification
+
+
+
+let gallery = global<TokenBalance<TokenType>>(owner).gallery;
+ensures result <==> is_in_gallery(gallery, token_id);
+
+
+
+
@@ -286,6 +402,21 @@ Returns whether the owner has a token with given id.
+
+
+
+Specification
+
+
+
+let gallery = get_token_balance_gallery<TokenType>(owner);
+let min_token_idx = find_token_index_by_id(gallery, token_id);
+ensures !is_in_gallery(gallery, token_id) ==> result == 0;
+ensures is_in_gallery(gallery, token_id) ==> result == gallery[min_token_idx].balance;
+
+
+
+
@@ -335,13 +466,54 @@ approved operator of the owner.
// Add tokens to `to`'s gallery
add_to_gallery<TokenType>(to, to_token);
- }
+ };
+
// TODO: add event emission
}
+
+
+
+Specification
+
+
+
+let owner = Signer::address_of(account);
+let gallery_owner = get_token_balance_gallery<TokenType>(owner);
+let gallery_to = get_token_balance_gallery<TokenType>(to);
+let post post_gallery_owner = get_token_balance_gallery<TokenType>(owner);
+let post post_gallery_to = get_token_balance_gallery<TokenType>(to);
+let id = GUID::create_id(creator, creation_num);
+let min_token_idx = find_token_index_by_id(gallery_owner, id);
+let min_token_idx_to = find_token_index_by_id(gallery_to, id);
+aborts_if amount <= 0;
+aborts_if !exists<TokenBalance<TokenType>>(owner);
+aborts_if !exists<TokenBalance<TokenType>>(to);
+aborts_if !is_in_gallery(gallery_owner, id);
+aborts_if amount > gallery_owner[min_token_idx].balance;
+aborts_if owner != to && is_in_gallery(gallery_to, id) && MAX_U64 - amount < gallery_to[min_token_idx_to].balance;
+ensures (gallery_owner[min_token_idx].balance == amount && to != owner) ==>
+ !is_in_gallery(post_gallery_owner, id);
+ensures gallery_owner[min_token_idx].balance > amount ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].id == id;
+ensures post_gallery_to[len(post_gallery_to) - 1].id == id;
+ensures (gallery_owner[min_token_idx].balance > amount && to != owner) ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance - amount;
+ensures (to != owner && !is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance == amount;
+ensures (to != owner && is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance ==
+ gallery_to[min_token_idx_to].balance + amount;
+ensures to == owner ==> post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance;
+
+
+
+
@@ -367,4 +539,18 @@ approved operator of the owner.
+
+
+
+Specification
+
+
+
+let addr = Signer::address_of(account);
+aborts_if exists<TokenBalance<TokenType>>(addr);
+ensures exists<TokenBalance<TokenType>>(addr);
+
+
+
+
diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiToken.mvsm b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiToken.mvsm
index 795aaf0f6c5fb..8db4715832701 100644
Binary files a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiToken.mvsm and b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiToken.mvsm differ
diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiTokenBalance.mvsm b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiTokenBalance.mvsm
index 5056a63dc6863..c7da2458dcd11 100644
Binary files a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiTokenBalance.mvsm and b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/source_maps/MultiTokenBalance.mvsm differ
diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiToken.move b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiToken.move
index 81c630ceb689c..54580bb7a269c 100644
--- a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiToken.move
+++ b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiToken.move
@@ -53,6 +53,18 @@ module MultiToken {
tokens: vector>,
}
+ spec fun get_tokens(addr: address): vector>{
+ global>(addr).tokens
+ }
+
+ spec fun is_in_tokens(tokens: vector>, token_id: GUID::ID): bool {
+ exists token in tokens: token.token_id.id == token_id
+ }
+
+ spec fun find_token_index_by_id(tokens: vector>, id: GUID::ID): u64 {
+ choose min i in range(tokens) where tokens[i].token_id.id == id
+ }
+
const ADMIN: address = @0xa550c18;
const MAX_U64: u64 = 18446744073709551615u64;
// Error codes
@@ -89,6 +101,15 @@ module MultiToken {
Vector::borrow(tokens, index).supply
}
+ spec supply {
+ let addr = GUID::id_creator_address(id);
+ let token_collection = get_tokens(addr);
+ let min_token_idx = find_token_index_by_id(token_collection,id);
+ aborts_if !exists>(addr);
+ aborts_if !is_in_tokens(token_collection, id);
+ ensures result == token_collection[min_token_idx].supply;
+ }
+
/// Extract the MultiToken data of the given token into a hot potato wrapper.
public fun extract_token(nft: &Token): TokenDataWrapper acquires TokenDataCollection {
let owner_addr = GUID::id_creator_address(&nft.id);
@@ -101,6 +122,19 @@ module MultiToken {
TokenDataWrapper { origin: owner_addr, index, metadata: Option::extract(item_opt) }
}
+ spec extract_token {
+ let addr = GUID::id_creator_address(nft.id);
+ let token_collection = get_tokens(addr);
+ let id = nft.id;
+ let min_token_idx = find_token_index_by_id(token_collection, id);
+ aborts_if !exists>(addr);
+ aborts_if token_collection[min_token_idx].metadata == Option::spec_none();
+ aborts_if !is_in_tokens(token_collection, id);
+ ensures result == TokenDataWrapper { origin: addr, index: min_token_idx,
+ metadata: Option::borrow(token_collection[min_token_idx].metadata)};
+ ensures get_tokens(addr)[min_token_idx].metadata == Option::spec_none();
+ }
+
/// Restore the token in the wrapper back into global storage under original address.
public fun restore_token(wrapper: TokenDataWrapper) acquires TokenDataCollection {
let TokenDataWrapper { origin, index, metadata } = wrapper;
@@ -111,11 +145,25 @@ module MultiToken {
Option::fill(item_opt, metadata);
}
+ spec restore_token {
+ let addr = wrapper.origin;
+ let token_collection = get_tokens(addr);
+ let item_opt = token_collection[wrapper.index].metadata;
+ aborts_if !exists>(addr);
+ aborts_if len(token_collection) <= wrapper.index;
+ aborts_if item_opt != Option::spec_none();
+ ensures get_tokens(addr)[wrapper.index].metadata == Option::spec_some(wrapper.metadata);
+ }
+
/// Finds the index of token with the given id in the gallery.
fun index_of_token(gallery: &vector>, id: &GUID::ID): Option {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].token_id.id != id;
+ };(i < len)}) {
if (GUID::eq_id(&Vector::borrow(gallery, i).token_id, id)) {
return Option::some(i)
};
@@ -124,6 +172,13 @@ module MultiToken {
Option::none()
}
+ spec index_of_token{
+ let min_token_idx = find_token_index_by_id(gallery, id);
+ let post res_id = Option::borrow(result);
+ ensures is_in_tokens(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ ensures result == Option::spec_none() <==> !is_in_tokens(gallery, id);
+ }
+
/// Join two multi tokens and return a multi token with the combined value of the two.
public fun join(token: &mut Token, other: Token) {
let Token { id, balance } = other;
@@ -132,6 +187,12 @@ module MultiToken {
token.balance = token.balance + balance
}
+ spec join{
+ aborts_if token.id != other.id with EWRONG_TOKEN_ID;
+ aborts_if MAX_U64 - token.balance < other.balance with ETOKEN_BALANCE_OVERFLOWS;
+ ensures token.balance == old(token).balance + other.balance;
+ }
+
/// Split the token into two tokens, one with balance `amount` and the other one with balance
public fun split(token: Token, amount: u64): (Token, Token) {
assert!(token.balance >= amount, EAMOUNT_EXCEEDS_TOKEN_BALANCE);
@@ -144,6 +205,13 @@ module MultiToken {
} )
}
+ spec split {
+ aborts_if token.balance < amount with EAMOUNT_EXCEEDS_TOKEN_BALANCE;
+ ensures result_1.balance == token.balance - amount;
+ ensures result_2.balance == amount;
+ ensures result_1.id == result_2.id;
+ }
+
/// Initialize this module, to be called in genesis.
public fun initialize_multi_token(account: signer) {
assert!(Signer::address_of(&account) == ADMIN, ENOT_ADMIN);
@@ -152,6 +220,13 @@ module MultiToken {
})
}
+ spec initialize_multi_token{
+ let addr = Signer::address_of(account);
+ aborts_if addr != ADMIN;
+ aborts_if exists(addr);
+ ensures exists(addr);
+ }
+
/// Create a` TokenData` that wraps `metadata` and with balance of `amount`
public fun create(
account: &signer, metadata: TokenType, content_uri: vector, amount: u64
@@ -177,5 +252,20 @@ module MultiToken {
);
Token { id, balance: amount }
}
+
+ spec create {
+ let addr = Signer::address_of(account);
+ let post post_tokens = get_tokens(addr);
+
+ aborts_if !exists(ADMIN);
+ aborts_if exists(addr) && global(addr).counter + 1 > MAX_U64;
+
+ ensures result.balance == amount;
+ ensures GUID::id_creator_address(result.id) == addr;
+ ensures exists>(addr);
+ ensures post_tokens[len(post_tokens) - 1] ==
+ TokenData {metadata: Option::spec_some(metadata), token_id: GUID::GUID {id: result.id}, content_uri, supply:amount};
+ }
+
}
}
diff --git a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiTokenBalance.move b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiTokenBalance.move
index ac56b85741875..6f998a7891e74 100644
--- a/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiTokenBalance.move
+++ b/diem-move/diem-framework/experimental/releases/artifacts/current/build/DiemExperimental/sources/MultiTokenBalance.move
@@ -12,6 +12,23 @@ module DiemFramework::MultiTokenBalance {
gallery: vector>
}
+ spec TokenBalance {
+ invariant forall i1 in range(gallery), i2 in range(gallery) where gallery[i1].id == gallery[i2].id:
+ i1 == i2;
+ }
+
+ spec fun get_token_balance_gallery(addr: address): vector>{
+ global>(addr).gallery
+ }
+
+ spec fun is_in_gallery(gallery: vector>, token_id: GUID::ID): bool {
+ exists i in range(gallery): gallery[i].id == token_id
+ }
+
+ spec fun find_token_index_by_id(gallery: vector>, id: GUID::ID): u64 {
+ choose i in range(gallery) where gallery[i].id == id
+ }
+
const MAX_U64: u128 = 18446744073709551615u128;
// Error codes
const EID_NOT_FOUND: u64 = 0;
@@ -22,6 +39,8 @@ module DiemFramework::MultiTokenBalance {
const ENOT_OPERATOR: u64 = 5;
const EINVALID_APPROVAL_TARGET: u64 = 6;
+
+
/// Add a token to the owner's gallery. If there is already a token of the same id in the
/// gallery, we combine it with the new one and make a token of greater value.
public fun add_to_gallery(owner: address, token: Token)
@@ -35,7 +54,27 @@ module DiemFramework::MultiTokenBalance {
MultiToken::join(&mut token, original_token);
};
let gallery = &mut borrow_global_mut>(owner).gallery;
- Vector::push_back(gallery, token)
+ Vector::push_back(gallery, token);
+ }
+
+
+
+ spec add_to_gallery {
+ let gallery = get_token_balance_gallery(owner);
+ let token_bal = token.balance;
+ let min_token_idx = find_token_index_by_id(gallery, token.id);
+ let balance = gallery[min_token_idx].balance;
+ let post post_gallery = get_token_balance_gallery(owner);
+
+ aborts_if !exists>(owner);
+ aborts_if is_in_gallery(gallery, token.id) && MAX_U64 - token.balance < balance;
+
+ ensures is_in_gallery(gallery, token.id) ==> len(gallery) == len(post_gallery);
+ ensures !is_in_gallery(gallery, token.id) ==> len(gallery) + 1 == len(post_gallery);
+
+ ensures is_in_gallery(gallery, token.id) ==> post_gallery[len(gallery) - 1].balance ==
+ token_bal + gallery[min_token_idx].balance;
+ ensures post_gallery[len(post_gallery) - 1].id == token.id;
}
/// Remove a token of certain id from the owner's gallery and return it.
@@ -48,11 +87,22 @@ module DiemFramework::MultiTokenBalance {
Vector::remove(gallery, Option::extract(&mut index_opt))
}
+ spec remove_from_gallery {
+ let gallery = get_token_balance_gallery(owner);
+ aborts_if !exists>(owner);
+ aborts_if !is_in_gallery(gallery, id);
+ ensures !is_in_gallery(get_token_balance_gallery(owner), id);
+ }
+
/// Finds the index of token with the given id in the gallery.
fun index_of_token(gallery: &vector>, id: &GUID::ID): Option {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].id != id;
+ };(i < len)}) {
if (MultiToken::id(Vector::borrow(gallery, i)) == *id) {
return Option::some(i)
};
@@ -61,11 +111,23 @@ module DiemFramework::MultiTokenBalance {
Option::none()
}
+ spec index_of_token{
+ let min_token_idx = choose min i in range(gallery) where gallery[i].id == id;
+ let post res_id = Option::borrow(result);
+ ensures is_in_gallery(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ ensures result == Option::spec_none() <==> !is_in_gallery(gallery, id);
+ }
+
/// Returns whether the owner has a token with given id.
public fun has_token(owner: address, token_id: &GUID::ID): bool acquires TokenBalance {
Option::is_some(&index_of_token(&borrow_global>(owner).gallery, token_id))
}
+ spec has_token {
+ let gallery = global>(owner).gallery;
+ ensures result <==> is_in_gallery(gallery, token_id);
+ }
+
public fun get_token_balance(owner: address, token_id: &GUID::ID
): u64 acquires TokenBalance {
let gallery = &borrow_global>(owner).gallery;
@@ -79,6 +141,13 @@ module DiemFramework::MultiTokenBalance {
}
}
+ spec get_token_balance {
+ let gallery = get_token_balance_gallery(owner);
+ let min_token_idx = find_token_index_by_id(gallery, token_id);
+ ensures !is_in_gallery(gallery, token_id) ==> result == 0;
+ ensures is_in_gallery(gallery, token_id) ==> result == gallery[min_token_idx].balance;
+ }
+
/// Transfer `amount` of token with id `GUID::id(creator, creation_num)` from `owner`'s
/// balance to `to`'s balance. This operation has to be done by either the owner or an
/// approved operator of the owner.
@@ -111,12 +180,60 @@ module DiemFramework::MultiTokenBalance {
// Add tokens to `to`'s gallery
add_to_gallery(to, to_token);
- }
+ };
+
// TODO: add event emission
}
+ spec transfer_multi_token_between_galleries {
+ let owner = Signer::address_of(account);
+ let gallery_owner = get_token_balance_gallery(owner);
+ let gallery_to = get_token_balance_gallery(to);
+ let post post_gallery_owner = get_token_balance_gallery(owner);
+ let post post_gallery_to = get_token_balance_gallery(to);
+
+ let id = GUID::create_id(creator, creation_num);
+
+ let min_token_idx = find_token_index_by_id(gallery_owner, id);
+ let min_token_idx_to = find_token_index_by_id(gallery_to, id);
+
+ aborts_if amount <= 0;
+ aborts_if !exists>(owner);
+ aborts_if !exists>(to);
+ aborts_if !is_in_gallery(gallery_owner, id);
+ aborts_if amount > gallery_owner[min_token_idx].balance;
+ aborts_if owner != to && is_in_gallery(gallery_to, id) && MAX_U64 - amount < gallery_to[min_token_idx_to].balance;
+
+ ensures (gallery_owner[min_token_idx].balance == amount && to != owner) ==>
+ !is_in_gallery(post_gallery_owner, id);
+
+ ensures gallery_owner[min_token_idx].balance > amount ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].id == id;
+ ensures post_gallery_to[len(post_gallery_to) - 1].id == id;
+
+ ensures (gallery_owner[min_token_idx].balance > amount && to != owner) ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance - amount;
+
+ ensures (to != owner && !is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance == amount;
+ ensures (to != owner && is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance ==
+ gallery_to[min_token_idx_to].balance + amount;
+
+ ensures to == owner ==> post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance;
+
+ }
+
public fun publish_balance(account: &signer) {
assert!(!exists>(Signer::address_of(account)), EBALANCE_ALREADY_PUBLISHED);
move_to(account, TokenBalance { gallery: Vector::empty() });
}
+
+ spec publish_balance {
+ let addr = Signer::address_of(account);
+ aborts_if exists>(addr);
+ ensures exists>(addr);
+ }
}
diff --git a/diem-move/diem-framework/experimental/sources/MultiToken.move b/diem-move/diem-framework/experimental/sources/MultiToken.move
index 81c630ceb689c..54580bb7a269c 100644
--- a/diem-move/diem-framework/experimental/sources/MultiToken.move
+++ b/diem-move/diem-framework/experimental/sources/MultiToken.move
@@ -53,6 +53,18 @@ module MultiToken {
tokens: vector>,
}
+ spec fun get_tokens(addr: address): vector>{
+ global>(addr).tokens
+ }
+
+ spec fun is_in_tokens(tokens: vector>, token_id: GUID::ID): bool {
+ exists token in tokens: token.token_id.id == token_id
+ }
+
+ spec fun find_token_index_by_id(tokens: vector>, id: GUID::ID): u64 {
+ choose min i in range(tokens) where tokens[i].token_id.id == id
+ }
+
const ADMIN: address = @0xa550c18;
const MAX_U64: u64 = 18446744073709551615u64;
// Error codes
@@ -89,6 +101,15 @@ module MultiToken {
Vector::borrow(tokens, index).supply
}
+ spec supply {
+ let addr = GUID::id_creator_address(id);
+ let token_collection = get_tokens(addr);
+ let min_token_idx = find_token_index_by_id(token_collection,id);
+ aborts_if !exists>(addr);
+ aborts_if !is_in_tokens(token_collection, id);
+ ensures result == token_collection[min_token_idx].supply;
+ }
+
/// Extract the MultiToken data of the given token into a hot potato wrapper.
public fun extract_token(nft: &Token): TokenDataWrapper acquires TokenDataCollection {
let owner_addr = GUID::id_creator_address(&nft.id);
@@ -101,6 +122,19 @@ module MultiToken {
TokenDataWrapper { origin: owner_addr, index, metadata: Option::extract(item_opt) }
}
+ spec extract_token {
+ let addr = GUID::id_creator_address(nft.id);
+ let token_collection = get_tokens(addr);
+ let id = nft.id;
+ let min_token_idx = find_token_index_by_id(token_collection, id);
+ aborts_if !exists>(addr);
+ aborts_if token_collection[min_token_idx].metadata == Option::spec_none();
+ aborts_if !is_in_tokens(token_collection, id);
+ ensures result == TokenDataWrapper { origin: addr, index: min_token_idx,
+ metadata: Option::borrow(token_collection[min_token_idx].metadata)};
+ ensures get_tokens(addr)[min_token_idx].metadata == Option::spec_none();
+ }
+
/// Restore the token in the wrapper back into global storage under original address.
public fun restore_token(wrapper: TokenDataWrapper) acquires TokenDataCollection {
let TokenDataWrapper { origin, index, metadata } = wrapper;
@@ -111,11 +145,25 @@ module MultiToken {
Option::fill(item_opt, metadata);
}
+ spec restore_token {
+ let addr = wrapper.origin;
+ let token_collection = get_tokens(addr);
+ let item_opt = token_collection[wrapper.index].metadata;
+ aborts_if !exists>(addr);
+ aborts_if len(token_collection) <= wrapper.index;
+ aborts_if item_opt != Option::spec_none();
+ ensures get_tokens(addr)[wrapper.index].metadata == Option::spec_some(wrapper.metadata);
+ }
+
/// Finds the index of token with the given id in the gallery.
fun index_of_token(gallery: &vector>, id: &GUID::ID): Option {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].token_id.id != id;
+ };(i < len)}) {
if (GUID::eq_id(&Vector::borrow(gallery, i).token_id, id)) {
return Option::some(i)
};
@@ -124,6 +172,13 @@ module MultiToken {
Option::none()
}
+ spec index_of_token{
+ let min_token_idx = find_token_index_by_id(gallery, id);
+ let post res_id = Option::borrow(result);
+ ensures is_in_tokens(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ ensures result == Option::spec_none() <==> !is_in_tokens(gallery, id);
+ }
+
/// Join two multi tokens and return a multi token with the combined value of the two.
public fun join(token: &mut Token, other: Token) {
let Token { id, balance } = other;
@@ -132,6 +187,12 @@ module MultiToken {
token.balance = token.balance + balance
}
+ spec join{
+ aborts_if token.id != other.id with EWRONG_TOKEN_ID;
+ aborts_if MAX_U64 - token.balance < other.balance with ETOKEN_BALANCE_OVERFLOWS;
+ ensures token.balance == old(token).balance + other.balance;
+ }
+
/// Split the token into two tokens, one with balance `amount` and the other one with balance
public fun split(token: Token, amount: u64): (Token, Token) {
assert!(token.balance >= amount, EAMOUNT_EXCEEDS_TOKEN_BALANCE);
@@ -144,6 +205,13 @@ module MultiToken {
} )
}
+ spec split {
+ aborts_if token.balance < amount with EAMOUNT_EXCEEDS_TOKEN_BALANCE;
+ ensures result_1.balance == token.balance - amount;
+ ensures result_2.balance == amount;
+ ensures result_1.id == result_2.id;
+ }
+
/// Initialize this module, to be called in genesis.
public fun initialize_multi_token(account: signer) {
assert!(Signer::address_of(&account) == ADMIN, ENOT_ADMIN);
@@ -152,6 +220,13 @@ module MultiToken {
})
}
+ spec initialize_multi_token{
+ let addr = Signer::address_of(account);
+ aborts_if addr != ADMIN;
+ aborts_if exists(addr);
+ ensures exists(addr);
+ }
+
/// Create a` TokenData` that wraps `metadata` and with balance of `amount`
public fun create(
account: &signer, metadata: TokenType, content_uri: vector, amount: u64
@@ -177,5 +252,20 @@ module MultiToken {
);
Token { id, balance: amount }
}
+
+ spec create {
+ let addr = Signer::address_of(account);
+ let post post_tokens = get_tokens(addr);
+
+ aborts_if !exists(ADMIN);
+ aborts_if exists(addr) && global(addr).counter + 1 > MAX_U64;
+
+ ensures result.balance == amount;
+ ensures GUID::id_creator_address(result.id) == addr;
+ ensures exists>(addr);
+ ensures post_tokens[len(post_tokens) - 1] ==
+ TokenData {metadata: Option::spec_some(metadata), token_id: GUID::GUID {id: result.id}, content_uri, supply:amount};
+ }
+
}
}
diff --git a/diem-move/diem-framework/experimental/sources/MultiTokenBalance.move b/diem-move/diem-framework/experimental/sources/MultiTokenBalance.move
index ac56b85741875..6f998a7891e74 100644
--- a/diem-move/diem-framework/experimental/sources/MultiTokenBalance.move
+++ b/diem-move/diem-framework/experimental/sources/MultiTokenBalance.move
@@ -12,6 +12,23 @@ module DiemFramework::MultiTokenBalance {
gallery: vector>
}
+ spec TokenBalance {
+ invariant forall i1 in range(gallery), i2 in range(gallery) where gallery[i1].id == gallery[i2].id:
+ i1 == i2;
+ }
+
+ spec fun get_token_balance_gallery(addr: address): vector>{
+ global>(addr).gallery
+ }
+
+ spec fun is_in_gallery(gallery: vector>, token_id: GUID::ID): bool {
+ exists i in range(gallery): gallery[i].id == token_id
+ }
+
+ spec fun find_token_index_by_id(gallery: vector>, id: GUID::ID): u64 {
+ choose i in range(gallery) where gallery[i].id == id
+ }
+
const MAX_U64: u128 = 18446744073709551615u128;
// Error codes
const EID_NOT_FOUND: u64 = 0;
@@ -22,6 +39,8 @@ module DiemFramework::MultiTokenBalance {
const ENOT_OPERATOR: u64 = 5;
const EINVALID_APPROVAL_TARGET: u64 = 6;
+
+
/// Add a token to the owner's gallery. If there is already a token of the same id in the
/// gallery, we combine it with the new one and make a token of greater value.
public fun add_to_gallery(owner: address, token: Token)
@@ -35,7 +54,27 @@ module DiemFramework::MultiTokenBalance {
MultiToken::join(&mut token, original_token);
};
let gallery = &mut borrow_global_mut>(owner).gallery;
- Vector::push_back(gallery, token)
+ Vector::push_back(gallery, token);
+ }
+
+
+
+ spec add_to_gallery {
+ let gallery = get_token_balance_gallery(owner);
+ let token_bal = token.balance;
+ let min_token_idx = find_token_index_by_id(gallery, token.id);
+ let balance = gallery[min_token_idx].balance;
+ let post post_gallery = get_token_balance_gallery(owner);
+
+ aborts_if !exists>(owner);
+ aborts_if is_in_gallery(gallery, token.id) && MAX_U64 - token.balance < balance;
+
+ ensures is_in_gallery(gallery, token.id) ==> len(gallery) == len(post_gallery);
+ ensures !is_in_gallery(gallery, token.id) ==> len(gallery) + 1 == len(post_gallery);
+
+ ensures is_in_gallery(gallery, token.id) ==> post_gallery[len(gallery) - 1].balance ==
+ token_bal + gallery[min_token_idx].balance;
+ ensures post_gallery[len(post_gallery) - 1].id == token.id;
}
/// Remove a token of certain id from the owner's gallery and return it.
@@ -48,11 +87,22 @@ module DiemFramework::MultiTokenBalance {
Vector::remove(gallery, Option::extract(&mut index_opt))
}
+ spec remove_from_gallery {
+ let gallery = get_token_balance_gallery(owner);
+ aborts_if !exists>(owner);
+ aborts_if !is_in_gallery(gallery, id);
+ ensures !is_in_gallery(get_token_balance_gallery(owner), id);
+ }
+
/// Finds the index of token with the given id in the gallery.
fun index_of_token(gallery: &vector>, id: &GUID::ID): Option {
let i = 0;
let len = Vector::length(gallery);
- while (i < len) {
+ while ({spec {
+ invariant i >= 0;
+ invariant i <= len(gallery);
+ invariant forall k in 0..i: gallery[k].id != id;
+ };(i < len)}) {
if (MultiToken::id(Vector::borrow(gallery, i)) == *id) {
return Option::some(i)
};
@@ -61,11 +111,23 @@ module DiemFramework::MultiTokenBalance {
Option::none()
}
+ spec index_of_token{
+ let min_token_idx = choose min i in range(gallery) where gallery[i].id == id;
+ let post res_id = Option::borrow(result);
+ ensures is_in_gallery(gallery, id) <==> (Option::is_some(result) && res_id == min_token_idx);
+ ensures result == Option::spec_none() <==> !is_in_gallery(gallery, id);
+ }
+
/// Returns whether the owner has a token with given id.
public fun has_token(owner: address, token_id: &GUID::ID): bool acquires TokenBalance {
Option::is_some(&index_of_token(&borrow_global>(owner).gallery, token_id))
}
+ spec has_token {
+ let gallery = global>(owner).gallery;
+ ensures result <==> is_in_gallery(gallery, token_id);
+ }
+
public fun get_token_balance(owner: address, token_id: &GUID::ID
): u64 acquires TokenBalance {
let gallery = &borrow_global>(owner).gallery;
@@ -79,6 +141,13 @@ module DiemFramework::MultiTokenBalance {
}
}
+ spec get_token_balance {
+ let gallery = get_token_balance_gallery(owner);
+ let min_token_idx = find_token_index_by_id(gallery, token_id);
+ ensures !is_in_gallery(gallery, token_id) ==> result == 0;
+ ensures is_in_gallery(gallery, token_id) ==> result == gallery[min_token_idx].balance;
+ }
+
/// Transfer `amount` of token with id `GUID::id(creator, creation_num)` from `owner`'s
/// balance to `to`'s balance. This operation has to be done by either the owner or an
/// approved operator of the owner.
@@ -111,12 +180,60 @@ module DiemFramework::MultiTokenBalance {
// Add tokens to `to`'s gallery
add_to_gallery(to, to_token);
- }
+ };
+
// TODO: add event emission
}
+ spec transfer_multi_token_between_galleries {
+ let owner = Signer::address_of(account);
+ let gallery_owner = get_token_balance_gallery(owner);
+ let gallery_to = get_token_balance_gallery(to);
+ let post post_gallery_owner = get_token_balance_gallery(owner);
+ let post post_gallery_to = get_token_balance_gallery(to);
+
+ let id = GUID::create_id(creator, creation_num);
+
+ let min_token_idx = find_token_index_by_id(gallery_owner, id);
+ let min_token_idx_to = find_token_index_by_id(gallery_to, id);
+
+ aborts_if amount <= 0;
+ aborts_if !exists>(owner);
+ aborts_if !exists>(to);
+ aborts_if !is_in_gallery(gallery_owner, id);
+ aborts_if amount > gallery_owner[min_token_idx].balance;
+ aborts_if owner != to && is_in_gallery(gallery_to, id) && MAX_U64 - amount < gallery_to[min_token_idx_to].balance;
+
+ ensures (gallery_owner[min_token_idx].balance == amount && to != owner) ==>
+ !is_in_gallery(post_gallery_owner, id);
+
+ ensures gallery_owner[min_token_idx].balance > amount ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].id == id;
+ ensures post_gallery_to[len(post_gallery_to) - 1].id == id;
+
+ ensures (gallery_owner[min_token_idx].balance > amount && to != owner) ==>
+ post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance - amount;
+
+ ensures (to != owner && !is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance == amount;
+ ensures (to != owner && is_in_gallery(gallery_to, id) )==>
+ post_gallery_to[len(post_gallery_to) - 1].balance ==
+ gallery_to[min_token_idx_to].balance + amount;
+
+ ensures to == owner ==> post_gallery_owner[len(post_gallery_owner) - 1].balance ==
+ gallery_owner[min_token_idx].balance;
+
+ }
+
public fun publish_balance(account: &signer) {
assert!(!exists>(Signer::address_of(account)), EBALANCE_ALREADY_PUBLISHED);
move_to(account, TokenBalance { gallery: Vector::empty() });
}
+
+ spec publish_balance {
+ let addr = Signer::address_of(account);
+ aborts_if exists>(addr);
+ ensures exists>(addr);
+ }
}