From 1c626e13d8a0404c90cef07e41051715b9a0e3e3 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sat, 26 Jun 2021 12:06:37 +0000 Subject: [PATCH] cosmwasm: Add simple token contract example --- examples/cosmwasm_simple_token/CMakeLists.txt | 17 +++ .../cosmwasm_simple_token/include/token.hpp | 54 +++++++ examples/cosmwasm_simple_token/src/token.cpp | 140 ++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 examples/cosmwasm_simple_token/CMakeLists.txt create mode 100644 examples/cosmwasm_simple_token/include/token.hpp create mode 100644 examples/cosmwasm_simple_token/src/token.cpp diff --git a/examples/cosmwasm_simple_token/CMakeLists.txt b/examples/cosmwasm_simple_token/CMakeLists.txt new file mode 100644 index 00000000..d26245d6 --- /dev/null +++ b/examples/cosmwasm_simple_token/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +project(cosmwasm_simple_token) + +find_package(blanc) + +# TODO: Separate cosmwasm toolchain configuration +include(EosioWasmToolchain) + +add_executable(token ${CMAKE_CURRENT_SOURCE_DIR}/src/token.cpp) + +target_include_directories(token PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# TODO: Add a convenient way to set build profile +target_compile_options(token PUBLIC --profile=cosmwasm) +target_link_options(token PUBLIC --profile=cosmwasm) + diff --git a/examples/cosmwasm_simple_token/include/token.hpp b/examples/cosmwasm_simple_token/include/token.hpp new file mode 100644 index 00000000..5cca1c32 --- /dev/null +++ b/examples/cosmwasm_simple_token/include/token.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +struct token { + std::string address; + uint128_t amount; + + COSMWASM_SERIALIZE(token, (address)(amount)) +}; + +struct instantiate_msg { + std::string name; + std::string symbol; + uint8_t decimals; + std::vector initial_balances; + + COSMWASM_SERIALIZE(instantiate_msg, (name)(symbol)(decimals)(initial_balances)) +}; + +struct transfer_msg { + std::string recipient; + uint128_t amount; + + COSMWASM_SERIALIZE2(transfer_msg, "transfer", (recipient)(amount)) +}; + +using execute_msg = std::variant; + +struct balance_msg { + std::string address; + + COSMWASM_SERIALIZE2(balance_msg, "balance", (address)) +}; + +struct token_info_msg { + COSMWASM_SERIALIZE2(token_info_msg, "token_info") +}; + +using query_msg = std::variant; + +struct balance_response { + uint128_t balance; + + COSMWASM_SERIALIZE(balance_response, (balance)) +}; + +struct token_info_response { + std::string name; + std::string symbol; + uint8_t decimals; + uint128_t total_supply; + + COSMWASM_SERIALIZE(token_info_response, (name)(symbol)(decimals)(total_supply)) +}; diff --git a/examples/cosmwasm_simple_token/src/token.cpp b/examples/cosmwasm_simple_token/src/token.cpp new file mode 100644 index 00000000..93847a66 --- /dev/null +++ b/examples/cosmwasm_simple_token/src/token.cpp @@ -0,0 +1,140 @@ +#include +#include "token.hpp" + +using namespace cosmwasm; + +class token_contract : public contract { +public: + using contract::contract; + + void instantiate( + const std::string& name, + const std::string& symbol, + uint8_t decimals, + std::vector initial_balances + ) { + auto total_supply = create_accounts(initial_balances); + _storage.set( + namespaces_with_key({}, to_bytes("token_info")), + token_info_response { + name, symbol, decimals, total_supply + }.as_bytes() + ); + } + + void transfer(const std::string& recipient, uint128_t amount) { + auto address = addr_validate(recipient); + sub_balance(_info.sender, amount); + add_balance(address, amount); + } + + bytes get_balance(const std::string& address) { + balance_response bres = { 0 }; + + auto s = _storage.get(namespaces_with_key({to_bytes("balance")}, to_bytes(address))); + if (s) { + bres.balance = from_bytes(*s); + } + + return to_bytes(bres); + } + + bytes get_token_info() { + return *_storage.get(namespaces_with_key({}, to_bytes("token_info"))); + } + + +private: + void add_balance(const std::string& address, const uint128_t& amount) { + uint128_t balance = 0; + + auto key = namespaces_with_key({to_bytes("balance")}, to_bytes(address)); + auto s = _storage.get(key); + if (s) { + balance = from_bytes(*s); + } + + uint128_t new_balance = balance + amount; + check(new_balance > balance); + _storage.set(key, to_bytes(new_balance)); + } + + void sub_balance(const std::string& address, const uint128_t& amount) { + uint128_t balance = 0; + + auto key = namespaces_with_key({to_bytes("balance")}, to_bytes(address)); + auto s = _storage.get(key); + if (s) { + balance = from_bytes(*s); + } + + uint128_t new_balance = balance - amount; + check(new_balance < balance); + _storage.set(key, to_bytes(new_balance)); + } + + uint128_t create_accounts(const std::vector& accounts) { + uint128_t total_supply = 0; + for (const auto& acc : accounts) { + total_supply += acc.amount; + auto address = addr_validate(acc.address); + add_balance(address, acc.amount); + } + return total_supply; + } +}; + + +extern "C" { + [[clang::export_name("instantiate")]] + region* instantiate(region* env_ptr, region* info_ptr, region* msg_ptr) { + contract_result res; + + auto e = json::from_region(env_ptr); + auto i = json::from_region(info_ptr); + auto m = json::from_region(msg_ptr); + auto c = token_contract(e, i); + + c.instantiate(m.name, m.symbol, m.decimals, m.initial_balances); + + return json::to_region(res).release(); + } + + [[clang::export_name("execute")]] + region* execute(region* env_ptr, region* info_ptr, region* msg_ptr) { + contract_result res; + + auto e = json::from_region(env_ptr); + auto i = json::from_region(info_ptr); + auto m = json::from_region(msg_ptr); + auto c = token_contract(e, i); + + std::visit(overloaded { + [&](const transfer_msg& msg) { + c.transfer(msg.recipient, msg.amount); + res.ok.attributes.push_back({"action", "transfer"}); + res.ok.attributes.push_back({"from", i.sender}); + res.ok.attributes.push_back({"to", msg.recipient}); + res.ok.attributes.push_back({"amount", std::to_string(msg.amount)}); + }, + }, m); + + return json::to_region(res).release(); + } + + [[clang::export_name("query")]] + region* query(region* env_ptr, region* msg_ptr) { + contract_result res; + + auto e = json::from_region(env_ptr); + auto m = json::from_region(msg_ptr); + auto c = token_contract(e, {}); + + std::visit(overloaded { + [&](balance_msg& msg) { res.ok = c.get_balance(msg.address); }, + [&](token_info_msg& msg) { res.ok = c.get_token_info(); }, + }, m); + + return json::to_region(res).release(); + } +}