зеркало из https://github.com/microsoft/eEVM.git
Add samples (#13)
* 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:
Родитель
bb6d764499
Коммит
287570c2ed
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
-----------------
|
||||
```
|
||||
|
|
@ -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"
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Загрузка…
Ссылка в новой задаче