Introduction
What is Move?
Move is a next-generation programming language designed for secure, sandboxed, and formally verified smart contract development. It is used across multiple blockchain platforms, including Aptos, and offers robust security features that make it particularly well-suited for managing and transferring assets on the blockchain. Move's design ensures that resources (assets) are managed safely, avoiding many common pitfalls found in other smart contract languages.
Why Move Over Solidity and Foundry?
Security: Move's resource-oriented programming model ensures that assets are managed securely, preventing unauthorized duplication or deletion. This model provides stronger guarantees about the correctness and safety of smart contracts compared to Solidity.
Formal Verification: Move allows for formal verification of smart contracts, providing mathematical guarantees about the behaviour of contracts.
Modularity: Move promotes modular and reusable code through its module system, making it easier to build and maintain complex smart contracts.
In contrast, Solidity, while widely used on Ethereum, is known for its complex security pitfalls and less formal guarantees. Foundry, although a powerful development framework for Solidity, inherits the limitations of the Solidity language itself.
Variables and Types
Move supports several basic types such as u8
, u64
, u128
, bool
, address
, and more. Here's an example of declaring variables and types:
module MyModule {
public fun variable_example(): u64 {
let my_u8: u8 = 10;
let my_u64: u64 = 1000;
let my_bool: bool = true;
// Returning a value
my_u64
}
}
Functions and Procedures
Functions in Move can return values or modify the state. They are defined using the fun
keyword.
- Pure Functions: These do not modify the blockchain state and only return values.
module MyModule {
public fun add(a: u64, b: u64): u64 {
a + b
}
}
- Procedures: These can modify the blockchain state.
module MyModule {
public fun update_value(account: &signer, new_value: u64) {
// Logic to update some state with new_value
}
}
Modules and Scripts
Modules define reusable code and encapsulate state and behaviour. Scripts are one-off transactions that can call functions in modules.
- Modules:
module MyModule {
struct Counter has key {
value: u64,
}
public fun create_counter(account: &signer) {
let counter = Counter { value: 0 };
move_to(account, counter);
}
public fun increment_counter(account: &signer) {
let counter = borrow_global_mut<Counter>(signer::address_of(account));
counter.value = counter.value + 1;
}
public fun get_counter(account: address): u64 {
let counter = borrow_global<Counter>(account);
counter.value
}
}
- Scripts:
script {
use 0x1::MyModule;
fun main(account: &signer) {
MyModule::create_counter(account);
MyModule::increment_counter(account);
let count = MyModule::get_counter(signer::address_of(account));
assert!(count == 1, 0);
}
}
In the example above, the module MyModule
defines a Counter
resource and functions to create, increment, and retrieve the counter value. The script demonstrates how to use these module functions.
5. Move Resource Model
Move's unique resource model ensures that resources (data that cannot be copied or discarded) are handled safely.
Overview of Resources
Resources are data types that can only exist in one location at a time and are managed by the blockchain's ownership rules.
module ResourceExample {
struct MyResource has key {
value: u64,
}
public fun create_resource(account: &signer) {
let resource = MyResource { value: 10 };
move_to(account, resource);
}
public fun use_resource(account: &signer) {
let resource = borrow_global_mut<MyResource>(signer::address_of(account));
resource.value = resource.value + 10;
}
public fun destroy_resource(account: &signer) {
let resource = move_from<MyResource>(signer::address_of(account));
// Resource is now taken out of the account, handle accordingly
}
}
The resource MyResource
can be created, used, and destroyed, ensuring that its lifecycle is managed properly.
Let's demonstrate what we have learned by writing a simple token contract to demonstrate move syntax and semantics
Sure! Let's write a comprehensive example to showcase Move's syntax and semantics, including variables and types, functions and procedures, and modules and scripts.
Example: A Simple Token Contract
In this example, we'll create a simple token contract that includes functionality to mint tokens, transfer tokens, and check balances. We'll cover the basics of variables, types, functions, and modules.
Step 1: Define the Module
First, we'll define a module for our token contract. The module will include a struct to represent the token and functions to manage it.
module SimpleToken {
use 0x1::Signer;
// Define the Token struct
struct Token has key {
balance: u64,
}
// Mint new tokens to an account
public fun mint(account: &signer, amount: u64) {
let token = borrow_global_mut<Token>(Signer::address_of(account));
token.balance = token.balance + amount;
}
// Transfer tokens from the sender to the recipient
public fun transfer(sender: &signer, recipient: address, amount: u64) {
let sender_token = borrow_global_mut<Token>(Signer::address_of(sender));
let recipient_token = borrow_global_mut<Token>(recipient);
assert!(sender_token.balance >= amount, 1);
sender_token.balance = sender_token.balance - amount;
recipient_token.balance = recipient_token.balance + amount;
}
// Get the balance of an account
public fun get_balance(account: address): u64 {
let token = borrow_global<Token>(account);
token.balance
}
// Initialize the Token for a given account
public fun initialize(account: &signer) {
move_to(account, Token { balance: 0 });
}
}
Step 2: Script to Interact with the Module
Next, we'll write a script to interact with our SimpleToken
module. This script will initialize the token for an account, mint some tokens, and perform a transfer.
script {
use 0x1::SimpleToken;
use 0x1::Signer;
fun main(account: &signer) {
// Initialize the token for the account
SimpleToken::initialize(account);
// Mint 1000 tokens to the account
SimpleToken::mint(account, 1000);
// Check the balance
let balance = SimpleToken::get_balance(Signer::address_of(account));
assert!(balance == 1000, 0);
// Transfer 500 tokens to another account
let recipient = 0x2;
SimpleToken::initialize(&Signer::create_signer_for_address(recipient));
SimpleToken::transfer(account, recipient, 500);
// Check the balances
let sender_balance = SimpleToken::get_balance(Signer::address_of(account));
let recipient_balance = SimpleToken::get_balance(recipient);
assert!(sender_balance == 500, 0);
assert!(recipient_balance == 500, 0);
}
}
Explanation
Module Definition:
module SimpleToken { ... }
: Defines theSimpleToken
module.struct Token has key { balance: u64, }
defines a structToken
with abalance
field.public fun mint(account: &signer, amount: u64) { ... }
mints new tokens into an account.public fun transfer(sender: &signer, recipient: address, amount: u64) { ... }
transfers tokens from the sender to the recipient.public fun get_balance(account: address): u64 { ... }
gets the balance of an account.public fun initialize(account: &signer) { ... }
initializes the token for an account.
Script Definition:
script { ... }
defines a script to interact with theSimpleToken
module.use 0x1::SimpleToken;
imports theSimpleToken
module.use 0x1::Signer;
imports theSigner
module.fun main(account: &signer) { ... }
: Main function of the script.Initializes the token for the account.
addsmain 1000 tokens to the account.
Check the balance.
Transfers 500 tokens to another account.
Checks the balances of both accounts.
This example showcases the basic syntax and semantics of the Move language, including variables, types, functions, procedures, and modules. You can expand upon this example by adding more functionality and testing it on the Aptos blockchain.
For more information as well as testing and deployment, please visit the documentation.