* const& code, clang-format pass

* Add hello_world and sum samples

* clang-format pass

* Initial commit of ERC20 sample

* Add random transactions

* Add comments

* Add const& to avoid unnecessary copies

* Add license headers

* Precise comments

* Initial samples README

* Updated readme, remove unnecessary bin-runtime

* README update

* Check result of hello_world, readme pass
This commit is contained in:
Eddy Ashton 2018-12-03 10:41:25 +00:00 коммит произвёл GitHub
Родитель bb6d764499
Коммит 287570c2ed
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 879 добавлений и 20 удалений

Просмотреть файл

@ -70,3 +70,19 @@ add_test(
if(NOT ENV{TEST_DIR})
set_tests_properties(evm_tests PROPERTIES ENVIRONMENT TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/test_cases)
endif()
function(add_sample name)
add_executable(${name}
samples/${name}/main.cpp
)
target_include_directories(${name} PRIVATE
${Boost_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(${name} evm)
endfunction()
add_sample(hello_world)
add_sample(sum)
add_sample(erc20)

Просмотреть файл

@ -1221,7 +1221,8 @@ namespace evm
{
// TODO: implement native extensions
throw Exception(
ET::notImplemented, "Precompiled contracts/native extensions are not implemented.");
ET::notImplemented,
"Precompiled contracts/native extensions are not implemented.");
}
decltype(auto) callee = gs.get(addr);

Просмотреть файл

@ -25,7 +25,7 @@ namespace evm
}
AccountState SimpleGlobalState::create(
const Address& addr, uint256_t balance, Code code)
const Address& addr, const uint256_t& balance, const Code& code)
{
const auto acc = accounts.emplace(
addr, std::make_pair(Account(addr, balance, code), SimpleStorage()));

Просмотреть файл

@ -28,7 +28,7 @@ namespace evm
AccountState get(const Address& addr) override;
AccountState create(
const Address& addr, uint256_t balance, Code code) override;
const Address& addr, const uint256_t& balance, const Code& code) override;
size_t num_accounts() override;

Просмотреть файл

@ -11,7 +11,7 @@ using namespace std;
namespace evm
{
string strip(string s)
string strip(const string& s)
{
return (s.size() >= 2 && s[1] == 'x') ? s.substr(2) : s;
}
@ -22,9 +22,9 @@ namespace evm
return strtoull(&s[0], nullptr, 16);
}
vector<uint8_t> to_bytes(string s)
vector<uint8_t> to_bytes(const string& _s)
{
s = evm::strip(s);
auto s = evm::strip(_s);
const size_t byte_len = (s.size() + 1) / 2; // round up
vector<uint8_t> v(byte_len);
@ -45,7 +45,7 @@ namespace evm
return v;
}
string to_hex_string(vector<uint8_t> bytes)
string to_hex_string(const vector<uint8_t>& bytes)
{
stringstream ss;

Просмотреть файл

@ -21,7 +21,8 @@ namespace evm
Code code;
Account() = default;
Account(const Address& address, const uint256_t& balance, Code code) :
Account(
const Address& address, const uint256_t& balance, const Code& code) :
address(address),
nonce(0),
balance(balance),
@ -32,7 +33,7 @@ namespace evm
const Address& address,
uint64_t nonce,
const uint256_t& balance,
Code code) :
const Code& code) :
address(address),
nonce(nonce),
balance(balance),

Просмотреть файл

@ -2,9 +2,9 @@
// Licensed under the MIT License.
#pragma once
#include <boost/endian/conversion.hpp>
#include <boost/functional/hash/hash.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/endian/conversion.hpp>
#include <limits>
#include <nlohmann/json.hpp>
#include <sstream>
@ -49,10 +49,11 @@ auto from_big_endian(const Iterator begin, const Iterator end)
return v;
}
template<typename Iterator>
template <typename Iterator>
inline void to_big_endian(uint256_t v, Iterator out)
{
// boost::multiprecision::export_bits() does not work here, because it doesn't support fixed width export.
// boost::multiprecision::export_bits() does not work here, because it doesn't
// support fixed width export.
uint64_t* o = reinterpret_cast<uint64_t*>(&*out);
constexpr uint64_t mask64 = 0xffffffff'ffffffff;

Просмотреть файл

@ -7,8 +7,9 @@
namespace evm
{
/**
* An Ethereum block descriptor; in particular, this is used to parse cpp-ethereum test cases.
/**
* An Ethereum block descriptor; in particular, this is used to parse
* cpp-ethereum test cases.
*/
struct Block
{

Просмотреть файл

@ -8,7 +8,7 @@ namespace evm
{
/**
* A smart contract runtime execption
*/
*/
class Exception : public std::exception
{
public:

Просмотреть файл

@ -41,7 +41,7 @@ namespace evm
*/
virtual AccountState get(const Address& addr) = 0;
virtual AccountState create(
const Address& addr, uint256_t balance, Code code) = 0;
const Address& addr, const uint256_t& balance, const Code& code) = 0;
virtual size_t num_accounts() = 0;

Просмотреть файл

@ -29,7 +29,7 @@ namespace evm
};
/**
* Ethereum bytecode processor.
* Ethereum bytecode processor.
*/
class Processor
{

Просмотреть файл

@ -55,9 +55,9 @@ namespace evm
Keccak_HashFinal(&hi, output);
}
std::string strip(std::string s);
std::vector<uint8_t> to_bytes(std::string s);
std::string to_hex_string(std::vector<uint8_t> bytes);
std::string strip(const std::string& s);
std::vector<uint8_t> to_bytes(const std::string& s);
std::string to_hex_string(const std::vector<uint8_t>& bytes);
Address generate_address(const Address& sender, uint64_t nonce);

110
samples/README.md Normal file
Просмотреть файл

@ -0,0 +1,110 @@
# Samples
These samples show some basic examples of apps which using eEVM. They are intended as an introduction; they do not test or document every opcode or feature. In particular they do not demonstrate any expensive computations or cross-contract calls.
They are compiled by the root CMakeLists.txt, and run from the command line.
## hello_world
This sample creates and calls a contract which returns the string "Hello world!".
#### Details
- Generates some random Ethereum-compliant addresses. Real addresses should be derived from public-private key-pairs or from the contract generation method specified in the Yellow Paper, but here they are essentially arbitrary identifiers so can be random.
- Constructs contract bytecode. Real smart contracts will generally be compiled from some higher-level language (such as [Solidity](https://solidity.readthedocs.io/en/v0.5.0/)), but this shows it is also possible to write by hand. When the contract is executed, it stores each byte of the string in EVM memory, then returns the stored memory range.
- Deploys the contract. To execute bytecode, it must be deployed to an address in the GlobalState.
- Executes the bytecode. This requires creating an `evm::Transaction` and a sending address, as all EVM execution is transactional.
- Checks and prints the result. Execution may throw exceptions or halt unexpectedly, and these should be handled by checking `evm::ExitReason evm::ExecResult::er`. On success, any output (sent by `RETURN` opcodes in the top level code) will be in `evm::ExecResult::output`.
#### Expected output
The compiled app can be run with no arguments:
```bash
$ ./hello_world
Hello world!
```
## sum
This sample creates a contract which sums a pair of arguments passed on the command line. The summation is done within the EVM, by the `ADD` op code.
#### Details
In addition to the concepts from [hello_world](#hello_world), this sample:
- Parses args from command line. This demonstrates some of the helpers from [util.h](../include/util.h), specifically `from_hex_str` and `to_hex_str` for reading and writing 256-bit EVM numbers (or 160-bit addresses).
- Produces bytecode containing 256-bit immediates. Although the bytecode and each op code is a single byte, the complete code may contain larger values (such as the immediate for a `PUSH32` instruction) which must be serialized.
- Produces verbose output. If the `-v` option is given, this sample will print the precise contract address and code contents.
#### Expected output
The compiled app expects 2 arguments, which will be treated as hex-encoded 256-bit unsigned numbers (arguments which are too long will be truncated, invalid arguments will generally parse to 0):
```bash
$ ./sum
Usage: ./sum [-v] hex_a hex_b
Prints sum of arguments (hex string representation of 256-bit uints)
$ ./sum C0C0 EDA
0xC0C0 + 0xEDA = 0xCF9A
```
## erc20
This sample demonstrates an [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) token contract compiled from [Solidity](https://solidity.readthedocs.io/en/v0.5.0/index.html). The contract is deployed, then multiple calls are made to transfer tokens between addresses. The contract source is in [ERC20.sol](erc20/ERC20.sol), with the compiled result in [ERC20_combined.json](erc20/ERC20_combined.json) produced by:
```bash
$ solc --evm-version homestead --combined-json bin,hashes --pretty-json --optimize ERC20.sol > ERC20_combined.json
```
#### Details
- Parses a contract definition. The compiled output is read from a file, then [nlohmann::json](https://github.com/nlohmann/json) is used to extract the relevant fields from json.
- Deploys contract from definition. This demonstrates basic ABI encoding of arguments. The "bin" field from the contract definition is the raw code for the constructor - it must be given a single argument then run to produce the actual contract state and code.
- Calls functions on a deployed contract. The function selectors are retrieved from the "hashes" field of the contract definition, their ABI-encoded arguments appended, and the result is passed as input to an EVM transaction. All function calls (totalSupply, balanceOf, transfer) are like this, and use `run_and_check_result` to wrap the execution in `evm::Processor::run`.
- Address storage. Addresses are individual identities, and may have associated balances or state in any number of contracts' storage - all of which is contained within `GlobalState`. Only the address must be retained and passed.
- State reporting. The contract (and entire network) state is embedded in `GlobalState`, but not in an easily readable form. Instead the state can be reconstructed by additional read-only transactions sent to the ERC20 contract, and those results converted to a human-readable representation of the returned values.
#### Expected output
The combined json file produced by solc should be passed as an argument:
```bash
$ ./erc20 ../samples/erc20/ERC20_combined.json
-- Initial state --
Total supply of tokens is: 1000
User balances:
1000 owned by 0x7B6...7EF (original contract creator)
0 owned by 0x53D...3F0
-------------------
Transferring 333 from 0x7B6...7EF to 0x53D...3F0 (succeeded)
Transferring 334 from 0x53D...3F0 to 0x7B6...7EF (failed)
-- After one transaction --
Total supply of tokens is: 1000
User balances:
667 owned by 0x7B6...7EF (original contract creator)
333 owned by 0x53D...3F0
---------------------------
Transferring 12 from 0x53D...3F0 to 0x7B6...7EF (succeeded)
Transferring 76 from 0x53D...3F0 to 0x7B6...7EF (succeeded)
<...>
Transferring 60 from 0x7B6...7EF to 0x53D...3F0 (succeeded)
Transferring 83 from 0xB2B...733 to 0x8EE...584 (failed)
-- Final state --
Total supply of tokens is: 1000
User balances:
391 owned by 0x7B6...7EF (original contract creator)
329 owned by 0x53D...3F0
160 owned by 0x223...3E7
3 owned by 0x8EE...584
16 owned by 0x590...CE7
22 owned by 0xA1D...A3A
79 owned by 0xB2B...733
-----------------
```

90
samples/erc20/ERC20.sol Normal file
Просмотреть файл

@ -0,0 +1,90 @@
pragma solidity ^0.4.0;
/*contract ERC20_Interface {
function totalSupply() public constant returns (uint256 supply);
function balanceOf(address _owner) public constant returns (uint256 balance);
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
function allowance(address _owner, address _spender) public constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}*/
contract ERC20Token {
uint256 supply;
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
constructor(uint256 s) public
{
supply = s;
balances[msg.sender] = supply;
}
// Get the total token supply
function totalSupply() public constant returns (uint256)
{
return supply;
}
// Get the account balance of another account with address _owner
function balanceOf(address _owner) public constant returns (uint256)
{
return balances[_owner];
}
// Send _value amount of tokens to address _to
function transfer(address _to, uint256 _value) public returns (bool)
{
if (balances[msg.sender] >= _value)
{
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
else
{
return false;
}
}
// Send _value amount of tokens from address _from to address _to
function transferFrom(address _from, address _to, uint256 _value) public returns (bool)
{
if (balances[_from] >= _value && allowances[_from][msg.sender] >= _value)
{
balances[_from] -= _value;
balances[_to] += _value;
allowances[_from][msg.sender] -= _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
else
{
return false;
}
}
// Allow _spender to withdraw from your account, multiple times, up to the
// _value amount. If this function is called again it overwrites the current
// allowance with _value
function approve(address _spender, uint256 _value) public returns (bool)
{
// We permit allowances to be larger than balances
allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
// Returns the amount which _spender is still allowed to withdraw from _owner
function allowance(address _owner, address _spender) public constant returns (uint256)
{
return allowances[_owner][_spender];
}
}

Просмотреть файл

@ -0,0 +1,19 @@
{
"contracts" :
{
"ERC20.sol:ERC20Token" :
{
"bin" : "608060405234801561001057600080fd5b5060405160208061040783398101604090815290516000818155338152600160205291909120556103c1806100466000396000f3006080604052600436106100775763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663095ea7b3811461007c57806318160ddd146100b457806323b872dd146100db57806370a0823114610105578063a9059cbb14610126578063dd62ed3e1461014a575b600080fd5b34801561008857600080fd5b506100a0600160a060020a0360043516602435610171565b604080519115158252519081900360200190f35b3480156100c057600080fd5b506100c96101d8565b60408051918252519081900360200190f35b3480156100e757600080fd5b506100a0600160a060020a03600435811690602435166044356101de565b34801561011157600080fd5b506100c9600160a060020a03600435166102c4565b34801561013257600080fd5b506100a0600160a060020a03600435166024356102df565b34801561015657600080fd5b506100c9600160a060020a036004358116906024351661036a565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a03831660009081526001602052604081205482118015906102295750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102b957600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102bd565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103625733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101d2565b5060006101d2565b600160a060020a039182166000908152600260209081526040808320939094168252919091522054905600a165627a7a7230582083ea8b5d8f51646e054848659eb58071898c2447706d0abf59b719c343d5d5ce0029",
"hashes" :
{
"allowance(address,address)" : "dd62ed3e",
"approve(address,uint256)" : "095ea7b3",
"balanceOf(address)" : "70a08231",
"totalSupply()" : "18160ddd",
"transfer(address,uint256)" : "a9059cbb",
"transferFrom(address,address,uint256)" : "23b872dd"
}
}
},
"version" : "0.4.24+commit.6ae8fb59.Windows.msvc"
}

357
samples/erc20/main.cpp Normal file
Просмотреть файл

@ -0,0 +1,357 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "evm/simpleglobalstate.h"
#include "include/opcode.h"
#include "include/processor.h"
#include <cassert>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <random>
#include <sstream>
#include <vector>
///////////////////////////////////////////////////////////////////////////////
//
// Util typedefs and functions
//
using Addresses = std::vector<evm::Address>;
struct Environment
{
evm::GlobalState& gs;
const evm::Address& owner_address;
const nlohmann::json& contract_definition;
};
size_t rand_range(size_t exclusive_upper_bound)
{
std::random_device rand_device;
std::mt19937 generator(rand_device());
std::uniform_int_distribution<size_t> dist(0, exclusive_upper_bound - 1);
return dist(generator);
}
uint256_t get_random_uint256(size_t bytes = 32)
{
std::vector<uint8_t> raw(bytes);
std::generate(raw.begin(), raw.end(), []() { return rand(); });
return from_big_endian(raw.begin(), raw.end());
}
evm::Address get_random_address()
{
return get_random_uint256(20);
}
///////////////////////////////////////////////////////////////////////////////
// Truncate 160-bit addresses to a more human-friendly length, retaining the
// start and end for identification
std::string short_name(const evm::Address& address)
{
const auto full_hex = to_hex_str(address);
return full_hex.substr(0, 5) + std::string("...") +
full_hex.substr(full_hex.size() - 3);
}
// Run input as an EVM transaction, check the result and return the output
std::vector<uint8_t> run_and_check_result(
Environment& env,
const evm::Address& from,
const evm::Address& to,
const evm::Code& input)
{
// Ignore any logs produced by this transaction
evm::NullLogHandler ignore;
evm::Transaction tx(from, ignore);
// Record a trace to aid debugging
evm::Trace tr;
evm::Processor p(env.gs);
// Run the transaction
const auto exec_result = p.run(tx, from, env.gs.get(to), input, 0u, &tr);
if (exec_result.er != evm::ExitReason::returned)
{
// Print the trace if nothing was returned
std::cerr << tr << std::endl;
if (exec_result.er == evm::ExitReason::threw)
{
// Rethrow to highlight any exceptions raised in execution
throw std::runtime_error(
"Execution threw an error: " + exec_result.exmsg);
}
throw std::runtime_error("Deployment did not return");
}
return exec_result.output;
}
// Modify code to append ABI-encoding of arg, suitable for passing to contract
// execution
void append_argument(std::vector<uint8_t>& code, const uint256_t& arg)
{
// To ABI encode a function call with a uint256_t (or Address) argument,
// simply append the big-endian byte representation to the code (function
// selector, or bin). ABI-encoding for more complicated types is more
// complicated, so not shown in this sample.
const auto pre_size = code.size();
code.resize(pre_size + 32u);
to_big_endian(arg, code.data() + pre_size);
}
// Deploy the ERC20 contract defined in env, with total_supply tokens. Return
// the address the contract was deployed to
evm::Address deploy_erc20_contract(
Environment& env, const uint256_t total_supply)
{
// Generate the contract address
const auto contract_address = evm::generate_address(env.owner_address, 0u);
// Get the binary constructor of the contract
auto contract_constructor = evm::to_bytes(env.contract_definition["bin"]);
// The constructor takes a single argument (total_supply) - append it
append_argument(contract_constructor, total_supply);
// Set this constructor as the contract's code body
auto contract = env.gs.create(contract_address, 0u, contract_constructor);
// Run a transaction to initialise this account
auto result =
run_and_check_result(env, env.owner_address, contract_address, {});
// Result of running the compiled constructor is the code that should be the
// contract's body (constructor will also have setup contract's Storage)
contract.acc.code = result;
return contract.acc.address;
}
// Get the total token supply by calling totalSupply on the contract_address
uint256_t get_total_supply(
Environment& env, const evm::Address& contract_address)
{
// Anyone can call totalSupply - prove this by asking from a randomly
// generated address
const auto caller = get_random_address();
const auto function_call =
evm::to_bytes(env.contract_definition["hashes"]["totalSupply()"]);
const auto output =
run_and_check_result(env, caller, contract_address, function_call);
return from_big_endian(output.begin(), output.end());
}
// Get the current token balance of target_address by calling balanceOf on
// contract_address
uint256_t get_balance(
Environment& env,
const evm::Address& contract_address,
const evm::Address& target_address)
{
// Anyone can call balanceOf - prove this by asking from a randomly generated
// address
const auto caller = get_random_address();
auto function_call =
evm::to_bytes(env.contract_definition["hashes"]["balanceOf(address)"]);
append_argument(function_call, target_address);
const auto output =
run_and_check_result(env, caller, contract_address, function_call);
return from_big_endian(output.begin(), output.end());
}
// Transfer tokens from source_address to target_address by calling transfer on
// contract_address
bool transfer(
Environment& env,
const evm::Address& contract_address,
const evm::Address& source_address,
const evm::Address& target_address,
const uint256_t& amount)
{
// To transfer tokens, the caller must be the intended source address
auto function_call = evm::to_bytes(
env.contract_definition["hashes"]["transfer(address,uint256)"]);
append_argument(function_call, target_address);
append_argument(function_call, amount);
std::cout << "Transferring " << amount << " from "
<< short_name(source_address) << " to "
<< short_name(target_address);
const auto output =
run_and_check_result(env, source_address, contract_address, function_call);
// Output should be a bool in a 32-byte vector.
if (output.size() != 32 || (output[31] != 0 && output[31] != 1))
{
throw std::runtime_error("Unexpected output from call to transfer");
}
const bool success = output[31] == 1;
std::cout << (success ? " (succeeded)" : " (failed)") << std::endl;
return success;
}
// Send N randomly generated token transfers. Some will be to new user addresses
template <size_t N>
void run_random_transactions(
Environment& env, const evm::Address& contract_address, Addresses& users)
{
const auto total_supply = get_total_supply(env, contract_address);
const auto transfer_max = (2 * total_supply) / N;
for (size_t i = 0; i < N; ++i)
{
const auto from_index = rand_range(users.size());
auto to_index = rand_range(users.size());
// Occasionally create new users and transfer to them. Also avoids
// self-transfer
if (from_index == to_index)
{
to_index = users.size();
users.push_back(get_random_address());
}
const auto amount = get_random_uint256() % transfer_max;
transfer(env, contract_address, users[from_index], users[to_index], amount);
}
}
// Print the total token supply and current token balance of each user, by
// sending transactions to the given contract_address
void print_erc20_state(
const std::string& heading,
Environment& env,
const evm::Address& contract_address,
const Addresses& users)
{
const auto total_supply = get_total_supply(env, contract_address);
using Balances = std::vector<std::pair<evm::Address, uint256_t>>;
Balances balances;
for (const auto& user : users)
{
balances.emplace_back(
std::make_pair(user, get_balance(env, contract_address, user)));
}
std::cout << heading << std::endl;
std::cout << "Total supply of tokens is: " << total_supply << std::endl;
std::cout << "User balances: " << std::endl;
for (const auto& pair : balances)
{
std::cout << " " << pair.second << " owned by " << short_name(pair.first);
if (pair.first == env.owner_address)
{
std::cout << " (original contract creator)";
}
std::cout << std::endl;
}
std::cout << std::string(heading.size(), '-') << std::endl;
}
// erc20/main
// - Parse args
// - Parse ERC20 contract definition
// - Deploy ERC20 contract
// - Transfer ERC20 tokens
// - Print summary of state
int main(int argc, char** argv)
{
srand(time(nullptr));
if (argc < 2)
{
std::cout << "Usage: " << argv[0] << " path/to/ERC20_combined.json"
<< std::endl;
return 1;
}
const uint256_t total_supply = 1000;
Addresses users;
// Create an account at a random address, representing the 'owner' who created
// the ERC20 contract (gets entire token supply initially)
const auto owner_address = get_random_address();
users.push_back(owner_address);
// Create one other initial user
const auto alice = get_random_address();
users.push_back(alice);
// Open the contract definition file
const auto contract_path = argv[1];
std::ifstream contract_fstream(contract_path);
if (!contract_fstream)
{
throw std::runtime_error(
std::string("Unable to open contract definition file ") + contract_path);
}
// Parse the contract definition from file
const auto contracts_definition = nlohmann::json::parse(contract_fstream);
const auto all_contracts = contracts_definition["contracts"];
const auto erc20_definition = all_contracts["ERC20.sol:ERC20Token"];
// Create environment
evm::SimpleGlobalState gs;
Environment env{gs, owner_address, erc20_definition};
// Deploy the ERC20 contract
const auto contract_address = deploy_erc20_contract(env, total_supply);
// Report initial state
print_erc20_state("-- Initial state --", env, contract_address, users);
std::cout << std::endl;
// Run a successful transaction
const auto first_transfer_amount = total_supply / 3;
const auto success = transfer(
env, contract_address, owner_address, alice, first_transfer_amount);
if (!success)
{
throw std::runtime_error("Expected transfer to succeed, but it failed");
}
// Trying to transfer more than is owned will fail (gracefully, returning
// false from the solidity function)
const auto failure = transfer(
env, contract_address, alice, owner_address, first_transfer_amount + 1);
if (failure)
{
throw std::runtime_error("Expected transfer to fail, but it succeeded");
}
// Report intermediate state
std::cout << std::endl;
print_erc20_state(
"-- After one transaction --", env, contract_address, users);
std::cout << std::endl;
// Create more users and run more transactions
run_random_transactions<20>(env, contract_address, users);
// Report final state
std::cout << std::endl;
print_erc20_state("-- Final state --", env, contract_address, users);
return 0;
}

Просмотреть файл

@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "evm/simpleglobalstate.h"
#include "include/opcode.h"
#include "include/processor.h"
#include <iostream>
std::vector<uint8_t> create_bytecode(const std::string& s)
{
std::vector<uint8_t> code;
constexpr uint8_t mdest = 0x0;
const uint8_t rsize = s.size() + 1;
// Store each byte in evm memory
uint8_t mcurrent = mdest;
for (const char& c : s)
{
code.push_back(evm::Opcode::PUSH1);
code.push_back(c);
code.push_back(evm::Opcode::PUSH1);
code.push_back(mcurrent++);
code.push_back(evm::Opcode::MSTORE8);
}
// Return
code.push_back(evm::Opcode::PUSH1);
code.push_back(rsize);
code.push_back(evm::Opcode::PUSH1);
code.push_back(mdest);
code.push_back(evm::Opcode::RETURN);
return code;
}
int main(int argc, char** argv)
{
// Create random addresses for sender and contract
std::vector<uint8_t> raw_address(160);
std::generate(
raw_address.begin(), raw_address.end(), []() { return rand(); });
const evm::Address sender =
from_big_endian(raw_address.begin(), raw_address.end());
std::generate(
raw_address.begin(), raw_address.end(), []() { return rand(); });
const evm::Address to =
from_big_endian(raw_address.begin(), raw_address.end());
// Create global state
evm::SimpleGlobalState gs;
// Create code
std::string hello_world("Hello world!");
const evm::Code code = create_bytecode(hello_world);
// Deploy contract to global state
const evm::AccountState contract = gs.create(to, 0, code);
// Create transaction
evm::NullLogHandler ignore;
evm::Transaction tx(sender, ignore);
// Create processor
evm::Processor p(gs);
// Execute code. All execution is associated with a transaction. This
// transaction is called by sender, executing the code in contract, with empty
// input (and no trace collection)
const evm::ExecResult e = p.run(tx, sender, contract, {}, 0, nullptr);
// Check the response
if (e.er != evm::ExitReason::returned)
{
std::cout << "Unexpected return code" << std::endl;
return 2;
}
// Create string from response data, and print it
const std::string response(reinterpret_cast<const char*>(e.output.data()));
if (response != hello_world)
{
throw std::runtime_error(
"Incorrect result.\n Expected: " + hello_world + "\n Actual: " + response);
return 3;
}
std::cout << response << std::endl;
return 0;
}

171
samples/sum/main.cpp Normal file
Просмотреть файл

@ -0,0 +1,171 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "evm/simpleglobalstate.h"
#include "include/opcode.h"
#include "include/processor.h"
#include <iostream>
int usage(const char* bin_name)
{
std::cout << "Usage: " << bin_name << " [-v] hex_a hex_b" << std::endl;
std::cout << "Prints sum of arguments (hex string representation of 256-bit "
"uints)"
<< std::endl;
return 1;
}
void push_uint256(std::vector<uint8_t>& code, const uint256_t& n)
{
// Append opcode
code.push_back(evm::Opcode::PUSH32);
// Resize code array
const size_t pre_size = code.size();
code.resize(pre_size + 32);
// Serialize number into code array
to_big_endian(n, code.begin() + pre_size);
}
std::vector<uint8_t> create_a_plus_b_bytecode(
const uint256_t& a, const uint256_t& b)
{
std::vector<uint8_t> code;
constexpr uint8_t mdest = 0x0; //< Memory start address for result
constexpr uint8_t rsize = 0x20; //< Size of result
// Push args and ADD
push_uint256(code, a);
push_uint256(code, b);
code.push_back(evm::Opcode::ADD);
// Store result
code.push_back(evm::Opcode::PUSH1);
code.push_back(mdest);
code.push_back(evm::Opcode::MSTORE);
// Return
code.push_back(evm::Opcode::PUSH1);
code.push_back(rsize);
code.push_back(evm::Opcode::PUSH1);
code.push_back(mdest);
code.push_back(evm::Opcode::RETURN);
return code;
}
int main(int argc, char** argv)
{
// Validate args, read verbose option
bool verbose = false;
size_t first_arg = 1;
if (argc < 3 || argc > 4)
{
return usage(argv[0]);
}
if (argc == 4)
{
if (strcmp(argv[1], "-v") != 0)
{
return usage(argv[0]);
}
verbose = true;
first_arg = 2;
}
srand(time(nullptr));
// Parse args
const uint256_t arg_a = from_hex_str(argv[first_arg]);
const uint256_t arg_b = from_hex_str(argv[first_arg + 1]);
if (verbose)
{
std::cout << "Calculating " << to_hex_str(arg_a) << " + "
<< to_hex_str(arg_b) << std::endl;
}
// Invent a random address to use as sender
std::vector<uint8_t> raw_address(160);
std::generate(
raw_address.begin(), raw_address.end(), []() { return rand(); });
const evm::Address sender =
from_big_endian(raw_address.begin(), raw_address.end());
// Generate a target address for the summing contract (this COULD be random,
// but here we use the scheme for Contract Creation specified in the Yellow
// Paper)
const evm::Address to = evm::generate_address(sender, 0);
// Create summing bytecode
const evm::Code code = create_a_plus_b_bytecode(arg_a, arg_b);
// Construct global state
evm::SimpleGlobalState gs;
// Populate the global state with the constructed contract
const evm::AccountState contract = gs.create(to, 0, code);
if (verbose)
{
std::cout << "Address " << to_hex_str(to)
<< " contains the following bytecode:" << std::endl;
std::cout << evm::to_hex_string(contract.acc.code) << std::endl;
}
// Construct a transaction object
evm::NullLogHandler ignore; //< Ignore any logs produced by this transaction
evm::Transaction tx(sender, ignore);
// Construct processor
evm::Processor p(gs);
if (verbose)
{
std::cout << "Executing a transaction from " << to_hex_str(sender) << " to "
<< to_hex_str(to) << std::endl;
}
// Run transaction
evm::Trace tr;
const evm::ExecResult e = p.run(
tx,
sender,
contract,
{}, //< No input - the arguments are hard-coded in the contract
0, //< No gas value
&tr //< Record execution trace
);
if (e.er != evm::ExitReason::returned)
{
std::cout << "Unexpected return code" << std::endl;
return 2;
}
if (verbose)
{
std::cout << "Execution completed, and returned a result of "
<< e.output.size() << " bytes" << std::endl;
}
const uint256_t result = from_big_endian(e.output.begin(), e.output.end());
std::cout << to_hex_str(arg_a);
std::cout << " + ";
std::cout << to_hex_str(arg_b);
std::cout << " = ";
std::cout << to_hex_str(result);
std::cout << std::endl;
if (verbose)
{
std::cout << "Done" << std::endl;
}
return 0;
}