Skip to content

Commit

Permalink
[RFC] Example-driven proposal for Move-based programmability
Browse files Browse the repository at this point in the history
  • Loading branch information
sblackshear committed Nov 26, 2021
1 parent 34a58eb commit c1ed7cc
Show file tree
Hide file tree
Showing 19 changed files with 1,126 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
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
4 changes: 4 additions & 0 deletions .gitignore
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
Expand Down
16 changes: 16 additions & 0 deletions fastx_programmability/Move.toml
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"
21 changes: 21 additions & 0 deletions fastx_programmability/README.md
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
```
59 changes: 59 additions & 0 deletions fastx_programmability/examples/CombinableObjects.move
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 fastx_programmability/examples/CustomObjectTemplate.move
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
}
}
92 changes: 92 additions & 0 deletions fastx_programmability/examples/EconMod.move
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
}
}
Loading

0 comments on commit c1ed7cc

Please sign in to comment.