diff --git a/CMakeLists.txt b/CMakeLists.txt index e98f9be709e470e32966c163694d5847abcbe1ff..abf1478936c8b72165008e6a4b5ce1d0e1255338 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ add_subdirectory( libraries ) add_subdirectory( programs ) add_subdirectory( plugins ) add_subdirectory( tests ) +add_subdirectory( tools ) if (ENABLE_INSTALLER) diff --git a/README.md b/README.md index 17061a8e682800c5805fdb4a44ecf48a587cd6b1..9731ef109b11e38abffec3c6e9469e481e322012 100644 --- a/README.md +++ b/README.md @@ -399,6 +399,12 @@ Before uploading a contract, you can verify that there is no current contract: code hash: 0000000000000000000000000000000000000000000000000000000000000000 ``` +Before you can upload currency contract you need to import its private key that you generated using `create key` command: + +```bash +./eosc wallet import PRIVATE_KEY_2 +``` + With an account for a contract created, you can upload a sample contract: ```bash diff --git a/contracts/skeleton/skeleton.abi b/contracts/skeleton/skeleton.abi new file mode 100644 index 0000000000000000000000000000000000000000..2654fb4b22143b3d1d11598e450367b587def10e --- /dev/null +++ b/contracts/skeleton/skeleton.abi @@ -0,0 +1,37 @@ +{ + "types": [{ + "newTypeName": "AccountName", + "type": "Name" + } + ], + "structs": [{ + "name": "transfer", + "base": "", + "fields": { + "from": "AccountName", + "to": "AccountName", + "amount": "UInt64" + } + },{ + "name": "account", + "base": "", + "fields": { + "account": "Name", + "balance": "UInt64" + } + } + ], + "actions": [{ + "action": "transfer", + "type": "transfer" + } + ], + "tables": [{ + "table": "account", + "type": "account", + "indextype": "i64", + "keynames" : ["account"], + "keytypes" : ["Name"] + } + ] +} diff --git a/contracts/skeleton/skeleton.cpp b/contracts/skeleton/skeleton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2e0dac4408dc65ccec2a46d4da0ec47facde395a --- /dev/null +++ b/contracts/skeleton/skeleton.cpp @@ -0,0 +1,47 @@ +#include /// defines transfer struct (abi) + +namespace TOKEN_NAME { + using namespace eos; + + /// When storing accounts, check for empty balance and remove account + void storeAccount( AccountName account, const Account& a ) { + if( a.isEmpty() ) { + /// value, scope + Accounts::remove( a, account ); + } else { + /// value, scope + Accounts::store( a, account ); + } + } + + void apply_currency_transfer( const TOKEN_NAME::Transfer& transfer ) { + requireNotice( transfer.to, transfer.from ); + requireAuth( transfer.from ); + + auto from = getAccount( transfer.from ); + auto to = getAccount( transfer.to ); + + from.balance -= transfer.quantity; /// token subtraction has underflow assertion + to.balance += transfer.quantity; /// token addition has overflow assertion + + storeAccount( transfer.from, from ); + storeAccount( transfer.to, to ); + } + +} // namespace TOKEN_NAME + +using namespace currency; + +extern "C" { + void init() { + storeAccount( N(currency), Account( CurrencyTokens(1000ll*1000ll*1000ll) ) ); + } + + /// The apply method implements the dispatch of events to this contract + void apply( uint64_t code, uint64_t action ) { + if( code == N(currency) ) { + if( action == N(transfer) ) + currency::apply_currency_transfer( currentMessage< TOKEN_NAME::Transfer >() ); + } + } +} diff --git a/contracts/skeleton/skeleton.hpp b/contracts/skeleton/skeleton.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a871e6ab94ae0aa63e0ec065afd3f03b5c6df287 --- /dev/null +++ b/contracts/skeleton/skeleton.hpp @@ -0,0 +1,101 @@ +#include +#include +#include + +/** + * Make it easy to change the account name the currency is deployed to. + */ +#ifndef TOKEN_NAME +#define TOKEN_NAME currency +#endif + +namespace TOKEN_NAME { + + /** + * @defgroup currencyapi Currency Contract API + * @brief Defines the curency contract + * @ingroup contractapi + * + * @{ + */ + + /** + * Defines a currency token + */ + typedef eos::token CurrencyTokens; + + /** + * Transfer requires that the sender and receiver be the first two + * accounts notified and that the sender has provided authorization. + */ + struct Transfer { + /** + * Account to transfer from + */ + AccountName from; + /** + * Account to transfer to + */ + AccountName to; + /** + * quantity to transfer + */ + CurrencyTokens quantity; + }; + + /** + * @brief row in Account table stored within each scope + */ + struct Account { + /** + Constructor with default zero quantity (balance). + */ + Account( CurrencyTokens b = CurrencyTokens() ):balance(b){} + + /** + * The key is constant because there is only one record per scope/currency/accounts + */ + const uint64_t key = N(account); + + /** + * Balance number of tokens in account + **/ + CurrencyTokens balance; + + /** + Method to check if accoutn is empty. + @return true if account balance is zero. + **/ + bool isEmpty()const { return balance.quantity == 0; } + }; + + /** + Assert statement to verify structure packing for Account + **/ + static_assert( sizeof(Account) == sizeof(uint64_t)+sizeof(CurrencyTokens), "unexpected packing" ); + + /** + Defines the database table for Account information + **/ + using Accounts = Table; + + /** + * Accounts information for owner is stored: + * + * owner/TOKEN_NAME/account/account -> Account + * + * This API is made available for 3rd parties wanting read access to + * the users balance. If the account doesn't exist a default constructed + * account will be returned. + * @param owner The account owner + * @return Account instance + */ + inline Account getAccount( AccountName owner ) { + Account account; + /// scope, record + Accounts::get( account, owner ); + return account; + } + +} /// @} /// currencyapi + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c89220a0c59ee1eead888bf4d60db4008dab7705 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1 @@ +configure_file("eoscpp" "${CMAKE_CURRENT_BINARY_DIR}" @ONLY) diff --git a/tools/eoscpp b/tools/eoscpp new file mode 100755 index 0000000000000000000000000000000000000000..f977aeb450ebf6314ce26c04f57f0412ee8e838f --- /dev/null +++ b/tools/eoscpp @@ -0,0 +1,85 @@ +#!/bin/bash -e + +function copy_skeleton { + cp -r /home/ubuntu/eos/contracts/skeleton/. $newname + + for file in $(find ./$newname -name 'skeleton.*') + do + mv "${file}" "`echo $file | sed 's/skeleton\./'"$newname"'./'`" + done + echo "skeleton $newname contract created" +} + +function build_contract { + output=$1 + shift + + workdir=`mktemp -d` + mkdir $workdir/built + + for file in $@; do + name=`basename $file` + filePath=`dirname $file` + + @WASM_CLANG@ -emit-llvm -O3 --std=c++14 --target=wasm32 -ffreestanding -nostdlib -fno-threadsafe-statics -fno-rtti -fno-exceptions -I @CMAKE_SOURCE_DIR@/contracts -I $filePath -c $file -o $workdir/built/$name + done + + @WASM_LLVM_LINK@ -o $workdir/linked.bc $workdir/built/* + @WASM_LLC@ --asm-verbose=false -o $workdir/assembly.s $workdir/linked.bc + @BINARYEN_BIN@/s2wasm -o $output -s 16384 $workdir/assembly.s + + rm -rf $workdir +} + +function print_help { + echo "Usage: $0 output.wast contract.cpp [other.cpp ...]" + echo " OR" + echo " $0 -n mycontract" + echo + echo "Options:" + echo " -n | --newcontract [name]" + echo " Create a new contract in the [name] folder, based on the example contract" +} + +OPTIONS=$(getopt --options=hn: --longoptions=help,newcontract: --name "$0" -- "$@") +if [[ $? -ne 0 ]]; then + # getopt failed + exit 2 +fi + +eval set -- "$OPTIONS" + +while true; do + case "$1" in + -n|--newcontract) + newname=$2 + shift 2 + ;; + -h|--help) + print_help + exit 1 + ;; + --) + shift + break + ;; + *) + echo "Unrecognized option: $1" + exit 1 + ;; + esac +done + +if [[ "x" == "x$newname" ]]; then + if [[ $# -le 1 ]]; then + print_help + exit 1 + fi + build_contract $@ +else + if [[ $# -ne 0 ]]; then + print_help + exit 1 + fi + copy_skeleton $newname +fi