-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[move][evm] Experiments for EVM Move Programming Model
This PR contains some rough experiments for a possible programming model for EVM in Move. Check the README.md for details. Closes: #10104
- Loading branch information
1 parent
c2688f9
commit 5af341e
Showing
6 changed files
with
263 additions
and
3 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,10 @@ | ||
[package] | ||
name = "EvmExamples" | ||
version = "1.5.0" | ||
|
||
[addresses] | ||
Std = "0x1" | ||
Evm = "0x2" | ||
|
||
[dependencies] | ||
MoveStdlib = { local = "../../move-stdlib" } |
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 @@ | ||
**Work in Progress** | ||
|
||
This directory contains (a growing set of) examples of "Move-on-EVM", a programming model in Move for EVM. | ||
|
||
- [Token.move](./sources/Token.move) contains an implementation of ERC20 which is intended to be compliant and callable | ||
from other EVM code (Move or otherwise). | ||
- [Faucet.move](./sources/Faucet.move) contains the faucet example from the Ethereum book. | ||
|
||
The basic programming model is as follows: | ||
|
||
- The module [Evm.move](./sources/Evm.move) contains the API of a Move contract to the EVM. It encapsulates access to | ||
the transaction context and other EVM builtins. | ||
- In the current model, *each Move EVM contract has its own isolated address space*. That is, `borrow_global` et. al | ||
work on memory private to this contract. This reflects the setup of the EVM most naturally, where storage between | ||
contracts cannot be shared apart from via accessor contract functions. | ||
- In order to allow a contract to store to its own private memory, there is currently a pseudo function | ||
`Evm::sign(addr)` which allows a contract to convert any address into a signer for its own private memory. Eventually, | ||
we may want to remove the requirement to have a signer for move_to in the EVM context. | ||
- Move EVM contracts use attributes to indicate the usage of structs for storage and events, and for functions to be | ||
callable from other contracts. It is expected that there is some codegen of Move from these attributes. Specifically, | ||
functions marked as `callable` have a generated API for cross-contract EVM call and delegate invocations. |
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,27 @@ | ||
/// Module which provides access to EVM functionality, including information about the executing transaction. | ||
//// | ||
/// This currently only represents a basic subset of what we may want to expose. | ||
module Evm::Evm { | ||
|
||
/// Returns the address of the executing contract. | ||
public native fun self(): address; | ||
|
||
/// Returns the address of the transaction sender. | ||
public native fun sender(): address; | ||
|
||
/// If this is a payable transaction, returns the value (in Wei) associated with it. | ||
/// TODO: need u256 | ||
public native fun value(): u128; | ||
|
||
/// Returns the balance, in Wei, of any account. | ||
public native fun balance(addr: address): u128; | ||
|
||
/// Transfers the given amount to the target account. | ||
public native fun transfer(addr: address, amount: u128); | ||
|
||
/// Emits an event. The type passed for `E` must be annotated with #[event]. | ||
public native fun emit<E>(e: E); | ||
|
||
/// Creates a signer for the contract's address space. | ||
public native fun sign(addr: address): &signer; | ||
} |
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,57 @@ | ||
#[contract] | ||
/// Faucet example in the Ethereum book. | ||
module 0x42::Faucet { | ||
use Evm::Evm::{sender, value, self, sign, balance, transfer, emit}; | ||
use Std::Errors; | ||
|
||
#[storage] | ||
struct State has key { | ||
owner: address, | ||
} | ||
|
||
#[event] | ||
struct WithdrawalEvent { | ||
to: address, | ||
amount: u128 | ||
} | ||
|
||
#[event] | ||
struct DepositEvent { | ||
from: address, | ||
amount: u128 | ||
} | ||
|
||
#[create] | ||
public fun create() { | ||
move_to<State>(sign(self()), State{owner: sender()}) | ||
} | ||
|
||
#[delete] | ||
public fun delete() acquires State { | ||
let state = borrow_global<State>(self()); | ||
assert!(sender() == state.owner, Errors::requires_address(0)); | ||
} | ||
|
||
#[receive, payable] | ||
public fun receive() { | ||
emit(DepositEvent{from: sender(), amount: value()}) | ||
} | ||
|
||
#[callable] | ||
public fun withdraw(amount: u128) acquires State { | ||
let state = borrow_global<State>(self()); | ||
|
||
// Don't allow to withdraw from self. | ||
assert!(state.owner != self(), Errors::invalid_argument(0)); | ||
|
||
// Limit withdrawal amount | ||
assert!(amount <= 100, Errors::invalid_argument(0)); | ||
|
||
// Funds must be available. | ||
assert!(balance(self()) >= amount, Errors::limit_exceeded(0)); | ||
|
||
// Transfer funds | ||
transfer(sender(), amount); | ||
emit(WithdrawalEvent{to: sender(), amount}) | ||
} | ||
} |
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,145 @@ | ||
#[contract] | ||
/// An implementation of ERC20. | ||
module Evm::ERC20 { | ||
use Evm::Evm::{sender, self, sign}; | ||
use Std::Errors; | ||
use Std::Vector; | ||
|
||
#[storage] | ||
/// Represents the state of this contract. This is located at `borrow_global<State>(self())`. | ||
struct State has key { | ||
decimals: u8, | ||
total_supply: u128, | ||
} | ||
|
||
#[storage] | ||
/// Represents the state of an account managed by this contract, located at | ||
/// `borrow_global<Account>(address)`. Notice that the storage of this resource | ||
/// is private to this contract, as each EVM contract manages its own Move address space. | ||
struct Account has key { | ||
/// The balance value. | ||
value: u128, | ||
/// The allowances this account has granted to other specified accounts. | ||
allowances: vector<Allowance> | ||
} | ||
|
||
/// How much a spender is allowed to use. | ||
struct Allowance has store { | ||
spender: address, | ||
amount: u128, | ||
} | ||
|
||
#[create] | ||
/// Constructor of this contract. | ||
public fun create(initial_amount: u128, decimals: u8) { | ||
// Initial state of contract | ||
move_to<State>(sign(self()), State{decimals, total_supply: initial_amount}); | ||
|
||
// Initialize senders balance with initial amount | ||
move_to<Account>(sign(sender()), Account{value: initial_amount, allowances: vector[]}); | ||
} | ||
|
||
#[callable, view] | ||
/// Returns the total supply of the token. | ||
public fun total_supply(): u128 acquires State { | ||
borrow_global<State>(self()).total_supply | ||
} | ||
|
||
#[callable, view] | ||
/// Returns the balance of an account. | ||
public fun balance_of(owner: address): u128 acquires Account { | ||
borrow_global<Account>(owner).value | ||
} | ||
|
||
#[callable, view] | ||
/// Returns the allowance an account owner has approved for the given spender. | ||
public fun allowance(owner: address, spender: address): u128 acquires Account { | ||
let allowances = &borrow_global<Account>(owner).allowances; | ||
let i = index_of_allowance(allowances, spender); | ||
if (i < Vector::length(allowances)) { | ||
Vector::borrow(allowances, i).amount | ||
} else { | ||
0 | ||
} | ||
} | ||
|
||
#[callable] | ||
/// Approves that the spender can spent the given amount on behalf of the calling account. | ||
public fun approve(spender: address, amount: u128) acquires Account { | ||
create_account_if_not_present(sender()); | ||
let allowances = &mut borrow_global_mut<Account>(sender()).allowances; | ||
mut_allowance(allowances, spender).amount = amount | ||
} | ||
|
||
#[callable] | ||
/// Transfers the amount from the sending account to the given account | ||
public fun transfer(to: address, amount: u128) acquires Account { | ||
assert!(sender() != to, Errors::invalid_argument(0)); | ||
do_transfer(sender(), to, amount) | ||
} | ||
|
||
|
||
#[callable] | ||
/// Transfers the amount on behalf of the `from` account to the given account. | ||
/// This evaluates and adjusts the allowance. | ||
public fun transfer_from(from: address, to: address, amount: u128) acquires Account { | ||
let allowances = &mut borrow_global_mut<Account>(from).allowances; | ||
let allowance = mut_allowance(allowances, sender()); | ||
assert!(allowance.amount >= amount, Errors::limit_exceeded(0)); | ||
allowance.amount = allowance.amount - amount; | ||
do_transfer(from, to, amount) | ||
} | ||
|
||
/// Helper function to perform a transfer of funds. | ||
fun do_transfer(from: address, to: address, amount: u128) acquires Account { | ||
create_account_if_not_present(from); | ||
create_account_if_not_present(to); | ||
let from_acc = borrow_global_mut<Account>(from); | ||
assert!(from_acc.value >= amount, Errors::limit_exceeded(0)); | ||
from_acc.value = from_acc.value - amount; | ||
let to_acc = borrow_global_mut<Account>(to); | ||
to_acc.value = to_acc.value + amount; | ||
} | ||
|
||
/// Helper function to find the index of an existing allowance. Returns length of the passed | ||
/// vector if not present. | ||
fun index_of_allowance(allowances: &vector<Allowance>, spender: address): u64 { | ||
let i = 0; | ||
let l = Vector::length(allowances); | ||
while (i < l) { | ||
if (Vector::borrow(allowances, i).spender == spender) { | ||
return i | ||
}; | ||
i = i + 1; | ||
}; | ||
return l | ||
} | ||
|
||
/// Helper function to return a mut ref to the allowance of a spender. | ||
fun mut_allowance(allowances: &mut vector<Allowance>, spender: address): &mut Allowance { | ||
let i = index_of_allowance(allowances, spender); | ||
if (i == Vector::length(allowances)) { | ||
Vector::push_back(allowances, Allowance{spender, amount: 0}) | ||
}; | ||
Vector::borrow_mut(allowances, i) | ||
} | ||
|
||
/// Helper function to create an account with a zero balance and no allowances. | ||
fun create_account_if_not_present(owner: address) { | ||
if (!exists<Account>(owner)) { | ||
move_to<Account>(sign(owner), Account{value: 0, allowances: vector[]}) | ||
} | ||
} | ||
|
||
// ============================================================================================================== | ||
// The following APIs will be automatically generated from the #[callable] function attributes. They | ||
// constitute the EVM level contract API. | ||
|
||
public native fun call_total_supply(contract: address): u128; | ||
public native fun call_allowance(contract: address, owner: address, spender: address): u128; | ||
public native fun call_approve(contract: address, spender: address, amount: u128); | ||
public native fun call_transfer(contract: address, to: address, amount: u128); | ||
public native fun call_transfer_from(contract: address, from: address, to: address, amount: u128); | ||
|
||
// ... and the same as delegate_XXXX APIs? | ||
} |
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