forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RFC] Example-driven proposal for Move-based programmability
- Loading branch information
1 parent
34a58eb
commit c1ed7cc
Showing
19 changed files
with
1,126 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Best-effort syntax highlighting for Move: just use Rust | ||
*.move linguist-language=Rust |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
# Rust build directory | ||
/target | ||
|
||
# Move build directory | ||
build | ||
|
||
.DS_Store | ||
|
||
# Thumbnails | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "FastX" | ||
version = "0.0.1" | ||
|
||
[dependencies] | ||
MoveStdlib = { git = "https://github.com/diem/diem.git", subdir="language/move-stdlib", rev = "56ab033cc403b489e891424a629e76f643d4fb6b" } | ||
|
||
[addresses] | ||
Std = "0x1" | ||
FastX = "0x2" | ||
Examples = "0x3" | ||
|
||
[dev-addresses] | ||
Std = "0x1" | ||
FastX = "0x2" | ||
Examples = "0x3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# FastX Programmability with Move | ||
|
||
This is a proof-of-concept Move standard library for FastX (`sources/`), along with several examples of programs that FastX users might want to write (`examples`). `CustomObjectTemplate.move` is a good starting point for understanding the proposed model. | ||
|
||
### Setup | ||
|
||
``` | ||
# install Move CLI | ||
cargo install --git https://github.com/diem/diem move-cli --branch main | ||
# put it in your PATH | ||
export PATH="$PATH:~/.cargo/bin" | ||
``` | ||
|
||
For reading/editing Move, your best bet is vscode + this [plugin](https://marketplace.visualstudio.com/items?itemName=move.move-analyzer). | ||
|
||
### Building | ||
|
||
``` | ||
# Inside the fastx_programmability/ dir | ||
move package -d build | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/// Example of objects that can be combined to create | ||
/// new objects | ||
module Examples::CombinableObjects { | ||
use Examples::TrustedCoin::EXAMPLE; | ||
use FastX::Authenticator::{Self, Authenticator}; | ||
use FastX::Coin::{Self, Coin}; | ||
use FastX::ID::ID; | ||
use FastX::Transfer; | ||
use FastX::TxContext::{Self, TxContext}; | ||
|
||
struct Ham has key { | ||
id: ID | ||
} | ||
|
||
struct Bread has key { | ||
id: ID | ||
} | ||
|
||
struct Sandwich has key { | ||
id: ID | ||
} | ||
|
||
/// Address selling ham, bread, etc | ||
const GROCERY: vector<u8> = b""; | ||
/// Price for ham | ||
const HAM_PRICE: u64 = 10; | ||
/// Price for bread | ||
const BREAD_PRICE: u64 = 2; | ||
|
||
/// Not enough funds to pay for the good in question | ||
const EINSUFFICIENT_FUNDS: u64 = 0; | ||
|
||
/// Exchange `c` for some ham | ||
public fun buy_ham(c: Coin<EXAMPLE>, ctx: &mut TxContext): Ham { | ||
assert!(Coin::value(&c) == HAM_PRICE, EINSUFFICIENT_FUNDS); | ||
Transfer::transfer(c, admin()); | ||
Ham { id: TxContext::new_id(ctx) } | ||
} | ||
|
||
/// Exchange `c` for some bread | ||
public fun buy_bread(c: Coin<EXAMPLE>, ctx: &mut TxContext): Bread { | ||
assert!(Coin::value(&c) == BREAD_PRICE, EINSUFFICIENT_FUNDS); | ||
Transfer::transfer(c, admin()); | ||
Bread { id: TxContext::new_id(ctx) } | ||
} | ||
|
||
/// Combine the `ham` and `bread` into a delicious sandwich | ||
public fun make_sandwich( | ||
ham: Ham, bread: Bread, ctx: &mut TxContext | ||
): Sandwich { | ||
let Ham { id: _ } = ham; | ||
let Bread { id: _ } = bread; | ||
Sandwich { id: TxContext::new_id(ctx) } | ||
} | ||
|
||
fun admin(): Authenticator { | ||
Authenticator::new(GROCERY) | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
fastx_programmability/examples/CustomObjectTemplate.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/// An example of a custom object with comments explaining the relevant bits | ||
module Examples::CustomObjectTemplate { | ||
use FastX::Authenticator::{Self, Authenticator}; | ||
use FastX::ID::ID; | ||
use FastX::Transfer; | ||
use FastX::TxContext::{Self, TxContext}; | ||
|
||
/// A custom fastX object. Every object must have the `key` attribute | ||
/// (indicating that it is allowed to be a key in the fastX global object | ||
/// pool), and must have a field `id: ID` corresponding to its fastX ObjId. | ||
/// Other object attributes present at the protocol level (authenticator, | ||
/// sequence number, TxDigest, ...) are intentionally not exposed here. | ||
struct Object has key { | ||
id: ID, | ||
/// Custom objects can have fields of arbitrary type... | ||
custom_field: u64, | ||
/// ... including other objects | ||
child_obj: ChildObject, | ||
/// ... and other global objects | ||
nested_obj: AnotherObject, | ||
} | ||
|
||
/// An object that can be stored inside global objects or other child | ||
/// objects, but cannot be placed in the global object pool on its own. | ||
/// Note that it doesn't need an ID field | ||
struct ChildObject has store { | ||
a_field: bool, | ||
} | ||
|
||
/// An object that can live either in the global object pool or as a nested | ||
/// object. | ||
struct AnotherObject has key, store { | ||
id: ID, | ||
} | ||
|
||
/// Example of updating an object. All Move fields are private, so the | ||
/// fields of `Object` can only be (directly) updated by code in this | ||
/// module. | ||
public fun write_field(o: &mut Object, v: u64) { | ||
if (some_conditional_logic()) { | ||
o.custom_field = v | ||
} | ||
} | ||
|
||
/// Example of transferring an object to a a new owner. A struct can only | ||
/// be transferred by the module that declares it. | ||
public fun transfer(o: Object, recipient: Authenticator) { | ||
assert!(some_conditional_logic(), 0); | ||
Transfer::transfer(o, recipient) | ||
} | ||
|
||
/// Simple getter | ||
public fun read_field(o: &Object): u64 { | ||
o.custom_field | ||
} | ||
|
||
/// Example of creating a object by deriving a unique ID from the current | ||
/// transaction and returning it to the caller (who may call functions | ||
/// from this module to read/write it, package it into another object, ...) | ||
public fun create(tx: &mut TxContext): Object { | ||
Object { | ||
id: TxContext::new_id(tx), | ||
custom_field: 0, | ||
child_obj: ChildObject { a_field: false }, | ||
nested_obj: AnotherObject { id: TxContext::new_id(tx) } | ||
} | ||
} | ||
|
||
/// Example of an entrypoint function to be embedded in a FastX | ||
/// transaction. The first argument of an entrypoint is always a | ||
/// special `TxContext` created by the runtime that is useful for deriving | ||
/// new id's or determining the sender of the transaction. | ||
/// After the `TxContext`, entrypoints can take struct types with the `key` | ||
/// attribute as input, as well as primitive types like ints, bools, ... | ||
/// | ||
/// A FastX transaction must declare the ID's of each object it will | ||
/// access + any primitive inputs. The runtime that processes the | ||
/// transaction fetches the values associated with the ID's, type-checks | ||
/// the values + primitive inputs against the function signature of the | ||
/// `main`, then calls the function with these values. | ||
/// | ||
/// If the script terminates successfully, the runtime collects changes to | ||
/// input objects + created objects + emitted events, increments the | ||
/// sequence number each object, creates a hash that commits to the | ||
/// outputs, etc. | ||
public fun main( | ||
ctx: &mut TxContext, | ||
to_read: &Object, | ||
to_write: &mut Object, | ||
to_consume: Object, | ||
// ... end objects, begin primitive type inputs | ||
int_input: u64, | ||
bytes_input: vector<u8> | ||
) { | ||
let v = read_field(to_read); | ||
write_field(to_write, v + int_input); | ||
transfer(to_consume, Authenticator::new(bytes_input)); | ||
// demonstrate creating a new object for the sender | ||
let sender = TxContext::get_authenticator(ctx); | ||
Transfer::transfer(create(ctx), sender) | ||
} | ||
|
||
fun some_conditional_logic(): bool { | ||
// placeholder for checks implemented in arbitrary Move code | ||
true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/// Mod of the economics of the SeaScape game. In the game, a `Hero` can onAly | ||
/// slay a `SeaMonster` if they have sufficient strength. This mod allows a | ||
/// player with a weak `Hero` to ask a player with a stronger `Hero` to slay | ||
/// the monster for them in exchange for some of the reward. | ||
module Examples::EconMod { | ||
use Examples::HeroMod::{Self, SeaMonster, RUM}; | ||
use Examples::Hero::Hero; | ||
use FastX::Authenticator::Authenticator; | ||
use FastX::Coin::{Self, Coin}; | ||
use FastX::ID::ID; | ||
use FastX::Transfer; | ||
use FastX::TxContext::{Self, TxContext}; | ||
|
||
/// Created by `monster_owner`, a player with a monster that's too strong | ||
/// for them to slay + transferred to a player who can slay the monster. | ||
/// The two players split the reward for slaying the monster according to | ||
/// the `helper_reward` parameter. | ||
struct HelpMeSlayThisMonster has key { | ||
id: ID, | ||
/// Monster to be slay by the owner of this object | ||
monster: SeaMonster, | ||
/// Identity of the user that originally owned the monster | ||
monster_owner: Authenticator, | ||
/// Number of tokens that will go to the helper. The owner will get | ||
/// the `monster` reward - `helper_reward` tokens | ||
helper_reward: u64, | ||
} | ||
|
||
// TODO: proper error codes | ||
/// The specified helper reward is too large | ||
const EINVALID_HELPER_REWARD: u64 = 0; | ||
|
||
/// Create an offer for `helper` to slay the monster in exchange for | ||
/// some of the reward | ||
public fun create( | ||
monster: SeaMonster, | ||
helper_reward: u64, | ||
helper: Authenticator, | ||
ctx: &mut TxContext, | ||
) { | ||
// make sure the advertised reward is not too large + that the owner | ||
// gets a nonzero reward | ||
assert!( | ||
HeroMod::monster_reward(&monster) > helper_reward, | ||
EINVALID_HELPER_REWARD | ||
); | ||
Transfer::transfer( | ||
HelpMeSlayThisMonster { | ||
id: TxContext::new_id(ctx), | ||
monster, | ||
monster_owner: TxContext::get_authenticator(ctx), | ||
helper_reward | ||
}, | ||
helper | ||
) | ||
} | ||
|
||
/// Helper should call this if they are willing to help out and slay the | ||
/// monster. | ||
public fun slay( | ||
hero: &Hero, wrapper: HelpMeSlayThisMonster, ctx: &mut TxContext, | ||
): Coin<RUM> { | ||
let HelpMeSlayThisMonster { | ||
id: _, | ||
monster, | ||
monster_owner, | ||
helper_reward | ||
} = wrapper; | ||
let owner_reward = HeroMod::slay(hero, monster); | ||
let helper_reward = Coin::withdraw(&mut owner_reward, helper_reward, ctx); | ||
Transfer::transfer(owner_reward, monster_owner); | ||
helper_reward | ||
} | ||
|
||
/// Helper can call this if they can't help slay the monster or don't want | ||
/// to, and are willing to kindly return the monster to its owner. | ||
public fun return_to_owner(wrapper: HelpMeSlayThisMonster) { | ||
let HelpMeSlayThisMonster { | ||
id: _, | ||
monster, | ||
monster_owner, | ||
helper_reward: _ | ||
} = wrapper; | ||
HeroMod::transfer_monster(monster, monster_owner) | ||
} | ||
|
||
/// Return the number of coins that `wrapper.owner` will earn if the | ||
/// the helper slays the monster in `wrapper. | ||
public fun owner_reward(wrapper: &HelpMeSlayThisMonster): u64 { | ||
HeroMod::monster_reward(&wrapper.monster) - wrapper.helper_reward | ||
} | ||
} |
Oops, something went wrong.